~ben/jour

jour/jour -rwxr-xr-x 6.5 KiB View raw
2c9f5734 — Ben Sima Print the correct date when entry is not found 3 years ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
#!/usr/bin/env python3.5
import argparse
import configparser
from datetime import datetime, timedelta
import logging
import os
from os.path import expanduser
import subprocess
import sys

__doc__ = \
"""
Write, read, and edit your journal. Calling jour with no arguments will open
today's journal entry for writing. For configuration options, see example.conf
"""

EDITOR = os.environ.get('EDITOR', '/usr/bin/vi')
PAGER = os.environ.get('PAGER', '/bin/more')
JOURNAL_DIR = os.environ.get('JOURNAL_DIR', expanduser("~/journal"))
CONFIG_DIR = os.environ.get('XDG_CONFIG_HOME', expanduser("~/.config"))
CONFIG_FILE_NAME = 'jour.conf'
DEFAULT_FORMAT = '{y}.{m}.{d}'


class Entry(object):
    """Represents a journal entry."""
    def __init__(self, date, journal_dir):
        """
        Create a new Entry object initialized to the given date and the
        corresponding journal file if it exists.
        """
        self.date = date
        self.path = os.path.join(os.sep, journal_dir, date)
        self.content = self._parse_content()
        self.tags = self._parse_tags() if self.content is not None else None

    def _parse_content(self):
        """Parses the file to a list of lines."""
        if os.path.exists(self.path):
            with open(self.path) as f:
                return f.readlines()
        else:
            return None

    def _parse_tags(self):
        """Checks the file for a tags section and lists the tags if any."""
        tags = [
            [w.strip() for w in line.strip("tags:").split(",")]
            for line in self.content if "tags" in line
        ]
        return tags[0] if len(tags) > 0 else None


class dateAction(argparse.Action):
    "TODO"
    def __init__(self, option_strings, dest, **kwargs):
        pass

    def __call__(self, parser, namespace, values, option_string):
        pass


def askToMakeDir(journal_dir, retries=4, reminder='Please try again!'):
    """
    Asks the user to create the journal directory.

    Will ask the user (retries) times or until they enter y or n.
    If the user enters anything other than y or n (reminder) will be printed.
    """
    while True:
        ok = input('Can not find journal dir. Wanna make it? ')
        if ok in ('y', 'ye', 'yes', 'Y'):
            os.makedirs(journal_dir)
            return True
        if ok in ('n', 'no', 'nop', 'nope', 'N'):
            raise NotADirectoryError("{0} does not exist.".format(journal_dir))
            return False
        retries = retries - 1
        if retries < 0:
            raise ValueError('invalid user response')
        print(reminder)


def check(journal_dir):
    """
    Checks whether the journal directory exists and prompts the user to create
    it if it doesn't.
    """
    if not os.path.exists(journal_dir):
        askToMakeDir(journal_dir)


def format_date(dt, conf):
    """Takes a datetime object and returns a string using the configured format."""
    def fmt(n):
        return "{0:02d}".format(n)

    s = list(map(fmt, [dt.year, dt.month, dt.day]))
    format_string = get_config_option(conf, 'filename_format')
    return format_string.format(y=s[0], m=s[1], d=s[2])


def load_config(config_file=None):
    """
    Load a configuration file and return a config parser (i.e. dictionary) with
    the settings.
    """
    # Set default config options
    config = configparser.ConfigParser(defaults={
        'filename_format': DEFAULT_FORMAT,
        'journal_dir': JOURNAL_DIR,
        'editor': EDITOR,
        'pager': PAGER
    })

    if config_file is None:
        config_file = os.path.join(os.sep, CONFIG_DIR, CONFIG_FILE_NAME)
    config.read(config_file)

    return config


def get_config_option(conf, key):
    """
    Get a configuration option from the config dictionary.

    Uses either the main section (if it exists) or the DEFAULT section if there
    is no main section. Will raise an exception if the key isn't found.
    """
    if 'main' in conf:
        return conf['main'].get(key)
    else:
        return conf['DEFAULT'].get(key)


# Commands ###################################################################
def list_entries(args, conf):
    """Prints a list of journal entries."""
    journal_dir = get_config_option(conf, 'journal_dir')
    entries = os.listdir(journal_dir)
    for e in sorted(entries):
        entry = Entry(e, journal_dir)
        if entry.tags is not None:
            print("{0} : {1}".format(entry.date, ", ".join(entry.tags)))
        else:
            print(entry.date)

    exit(0)


def read_entry(args, conf):
    """Prints today's journal entry."""
    journal_dir = get_config_option(conf, 'journal_dir')
    pager = get_config_option(conf, 'pager')
    e = Entry(args.date, journal_dir)
    if e.content is not None:
        print(e.path)
        subprocess.call([pager, e.path])
        exit(0)
    else:
        print("No journal entry found for {}.".format(args.date))
        exit(1)


def write_entry(args, conf):
    """Opens today's journal entry in the editor."""
    journal_dir = get_config_option(conf, 'journal_dir')
    editor = get_config_option(conf, 'editor')
    e = Entry(args.date, journal_dir)
    subprocess.call([editor, e.path])
    exit(0)


# CLI #######################################################################
def main():
    """Main function"""
    cli = argparse.ArgumentParser(
        formatter_class=argparse.RawDescriptionHelpFormatter,
        description=__doc__
    )

    cli.add_argument(
        '-d',
        '--date',
        default=datetime.now().strftime('%Y-%m-%d'),
        help="Set the date for the entry (YYYY-MM-DD). Defaults to today."
    )

    cli.add_argument(
        '-c',
        '--config',
        default=None,
        help="Full path to configuration file."
    )

    cli.add_argument(
        '-y',
        '--yesterday',
        action='store_true',
        help="Open yesterday's journal entry."
    )

    commands = cli.add_subparsers(dest="command")

    commands.add_parser('ls').set_defaults(command=list_entries)
    commands.add_parser('read').set_defaults(command=read_entry)

    args = cli.parse_args()

    conf = load_config(args.config)

    # Reformat the date according to the config file
    if args.yesterday:
        args.date = format_date(datetime.now() - timedelta(days=1), conf)
    elif args.date:
        args.date = format_date(datetime.strptime(args.date, '%Y-%m-%d'), conf)

    if not args.command:
        check(get_config_option(conf, 'journal_dir'))
        write_entry(args, conf)
    else:
        args.command(args, conf)

if __name__ == '__main__':
    main()