-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathnote
executable file
·195 lines (170 loc) · 6.01 KB
/
note
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
#!/usr/bin/env python3
"""Work with notes"""
from __future__ import annotations
import argparse
from datetime import date
from os import getenv
from pathlib import Path
import subprocess
import sys
from tempfile import NamedTemporaryFile
class Note:
"""Class to store details and modification methods of a note
Attributes:
note_name: The name of the note
filetype: The file extension of the note (md, txt, etc)
note_dir: The directory that the note is located in
"""
def __init__(self, note_name: str, filetype: str, note_dir: str):
"""Initialize variables"""
self.note_name = note_name
self.filetype = filetype
self.note_dir = note_dir
self.file: Path
if "." in self.note_name:
self.file = Path(f"{self.note_dir}/{self.note_name}")
else:
self.file = Path(f"{self.note_dir}/{self.note_name}.{self.filetype}")
def create_note(self, editor: str) -> None:
"""Creates a note
Attributes:
editor: The name of the text editor to use for the note
"""
try:
self.file.parent.mkdir(parents=True, exist_ok=True)
subprocess.run([editor, "--", self.file], check=True)
except PermissionError as err:
raise PermissionError from err
except subprocess.CalledProcessError as err:
raise NotImplementedError from err
def get_notes(self) -> dict[str, str]:
"""Gets a dictionary of all available notes in note_dir
Returns:
A dictionary where the keys are note names and the values are strings to the
note's filepath
"""
notes: dict = {}
for note in Path(self.note_dir).iterdir():
if not note.is_file():
continue
notes[note.name] = str(note.resolve())
return notes
def list_notes(self) -> None:
"""Prints all notes available in note_dir"""
notes: dict[str, str] = self.get_notes()
print(f"Notes in {self.note_dir}:")
for note in notes:
print(note)
def select_note(self, prompt: str) -> Path:
"""Prompts the user to select a note from note_dir with fzf
Attributes:
prompt: The prompt for fzf to use during selection
Returns:
A Path object of the note
"""
notes: dict[str, str] = self.get_notes()
selected_note: str = ""
cmd: str = "cat {} | fzf +m --ansi --prompt='{}' --preview='cat {}/{{}}'"
with NamedTemporaryFile(mode="w+", encoding="utf-8") as tmpfile:
try:
# add note options to tmpfile for piping
for note in notes:
tmpfile.write(note + "\n")
tmpfile.seek(0)
selected_note = (
subprocess.check_output(
cmd.format(tmpfile.name, prompt, self.note_dir),
shell=True,
)
.decode(sys.stdout.encoding)
.strip()
)
except subprocess.CalledProcessError as err:
# exit peacefully if the user doesn't select a note
if not selected_note:
raise SystemExit from err
raise err
return Path(notes[selected_note])
def view_note(self) -> None:
"""Prints a note to the terminal"""
try:
with self.file.open(encoding="utf-8") as file:
print(file.read())
except FileNotFoundError as err:
print_error(f"{self.file} does not exist")
raise SystemExit(1) from err
except PermissionError as err:
raise err
def print_error(msg: str, warning: str = "ERR") -> None:
"""Prints an error message to stderr
Attributes:
msg: The message to print to stderr
warning: The warning to prepend to the message, defaults to 'ERR'
"""
print(f"[{warning}] {msg}", file=sys.stderr)
def parse_args() -> argparse.Namespace:
"""Parses arguments with argparse
Returns:
The namespace of arguments
"""
parser: argparse.ArgumentParser = argparse.ArgumentParser()
actions = parser.add_mutually_exclusive_group()
actions.add_argument(
"-d", "--delete", action="store_true", help="Delete an existing note"
)
actions.add_argument(
"-e", "--edit", action="store_true", help="Edit an existing note"
)
actions.add_argument(
"-v", "--view", action="store_true", help="View an existing note"
)
parser.add_argument(
"-f",
"--filetype",
default="md",
help="Set the filetype of the new note (default: markdown)",
)
parser.add_argument(
"--editor",
default=getenv("EDITOR"),
help="Chose the editor to use. (default: $EDITOR)",
)
actions.add_argument(
"-l", "--list", action="store_true", help="List all created notes"
)
parser.add_argument(
"--note_dir",
default=f"{getenv('HOME')}/documents/personal/notes",
help="Directory to store the note in",
)
parser.add_argument(
"note",
nargs="?",
default=str(date.today()),
help="The name of the note (default: today's date)",
)
return parser.parse_args()
def main():
"""Main control flow for the program"""
args: argparse.Namespace = parse_args()
note: Note = Note(args.note, args.filetype, args.note_dir)
if args.delete:
try:
note.file = note.select_note("delete > ")
note.file.unlink()
print(f"Deleted '{note.file}'")
return
except PermissionError as err:
raise PermissionError from err
elif args.edit:
note.file = note.select_note("edit > ")
elif args.list:
note.list_notes()
raise SystemExit
else:
if args.view:
note.view_note()
raise SystemExit
note.create_note(args.editor)
if __name__ == "__main__":
main()