Last active
February 5, 2022 19:30
-
-
Save TimelessP/c6b0c20b314f6213ced329286fa6d4bf to your computer and use it in GitHub Desktop.
editquill - a super-minimal, line-mode, sandboxed text editor utility
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
editquill - a super-minimal, line-mode, sandboxed text editor utility | |
created by @TimelessP, 2022-02-05 | |
MIT licence | |
https://gist.github.com/TimelessP/c6b0c20b314f6213ced329286fa6d4bf | |
""" | |
import copy | |
import datetime | |
import os | |
import pickle | |
import re | |
import shutil | |
import sys | |
import textwrap | |
from dataclasses import dataclass | |
from typing import List, Optional, Dict | |
@dataclass | |
class EditQuillFile: | |
title: str | |
text_lines: List[str] | |
date_created = datetime.datetime.utcnow() | |
date_modified = datetime.datetime.utcnow() | |
class EditQuill: | |
def __init__(self, allow_save: bool = False, allow_load: bool = False, | |
allow_remove: bool = False, | |
storage: Optional[Dict[str, EditQuillFile]] = None): | |
self.allow_save = allow_save | |
self.allow_remove = allow_remove | |
self.allow_load = allow_load | |
self.storage = storage if storage else {} | |
self.current_file = EditQuillFile(title="", text_lines=[]) | |
self.stdout = sys.stdout | |
def run(self): | |
while True: | |
if self.current_file.title: | |
self.output(f"File: {self.current_file.title}") | |
action = input("Action (h for help)? ") | |
if action == "h": | |
self._help() | |
elif action == "q": | |
break | |
elif action == "n": | |
self.current_file = EditQuillFile(title="", text_lines=[]) | |
elif action == "s" and self.allow_save: | |
self._save_file() | |
elif action == "f" and self.allow_save: | |
self._save_file(save_as=True) | |
elif action == "r" and self.allow_remove: | |
self._remove_file() | |
elif action == "l" and self.allow_load: | |
self._load_file() | |
elif action == "c": | |
self._change_line() | |
elif action == "d": | |
self._delete_line() | |
elif action == "i": | |
self._insert_line() | |
elif action == "a": | |
self._append_line() | |
elif action == "v": | |
self._view() | |
else: | |
self.output("Invalid action; try `h` for help on available actions.") | |
def output(self, message): | |
max_width = shutil.get_terminal_size().columns | |
wrapped_text = os.linesep.join(textwrap.wrap(message, max_width)) + os.linesep | |
self.stdout.write(wrapped_text) | |
def _save_file(self, save_as=False): | |
if save_as or not self.current_file.title: | |
self.current_file.title = input("File title? ").strip() | |
if not self.current_file.title: | |
return | |
self.current_file.date_modified = datetime.datetime.utcnow() | |
self.storage[self.current_file.title] = copy.deepcopy(self.current_file) | |
self.output(f"File saved to: {self.current_file.title}") | |
def _load_file(self): | |
self.output(os.linesep.join(self.storage)) | |
title = input("Load file title? ") | |
if title not in self.storage: | |
self.output("File not found.") | |
return | |
self.current_file = copy.deepcopy(self.storage[title]) | |
self.output(f"Loaded file: {self.current_file.title}") | |
def _change_line(self): | |
line_number_choice = input("Change line number? ") | |
if not re.search(r"^[0-9]+$", line_number_choice) or \ | |
not 1 <= int(line_number_choice) <= len(self.current_file.text_lines): | |
self.output("Invalid line number.") | |
return | |
line_number = int(line_number_choice) | |
self.output(f"{line_number}:\t{self.current_file.text_lines[line_number - 1]}") | |
self.current_file.text_lines[line_number - 1] = input("Text? ") | |
def _delete_line(self): | |
line_number_choice = input("Delete line number? ") | |
if not re.search(r"^[0-9]+$", line_number_choice) or \ | |
not 1 <= int(line_number_choice) <= len(self.current_file.text_lines): | |
self.output("Invalid line number.") | |
return | |
line_number = int(line_number_choice) | |
self.output(f"{line_number}:\t{self.current_file.text_lines[line_number - 1]}") | |
self.output(f"Line {line_number} deleted.") | |
del self.current_file.text_lines[line_number - 1] | |
def _insert_line(self): | |
line_number_choice = input("Insert before line number? ") | |
if not re.search(r"^[0-9]+$", line_number_choice) or \ | |
not 1 <= int(line_number_choice) <= len(self.current_file.text_lines): | |
self.output("Invalid line number.") | |
return | |
line_number = int(line_number_choice) | |
self.current_file.text_lines.insert(line_number - 1, input("Text? ")) | |
def _append_line(self): | |
line = input("Text? ") | |
self.current_file.text_lines.append(line) | |
def _view(self): | |
for index, line in enumerate(self.current_file.text_lines): | |
self.output(f"{index + 1}:\t{line}") | |
def _help(self): | |
self.output("List of available actions:") | |
self.output("h - help") | |
self.output("n - new file") | |
if self.allow_save: | |
self.output("s - save file") | |
self.output("f - save file as a different title") | |
if self.allow_remove: | |
self.output("r - remove file") | |
if self.allow_load: | |
self.output("l - load file") | |
self.output("c - change a line of text") | |
self.output("d - delete a line of text") | |
self.output("i - insert a line of text") | |
self.output("a - append a line of text") | |
self.output("v - view the file") | |
self.output("q - quit") | |
@classmethod | |
def storage_factory(cls): | |
new_storage: Dict[str, EditQuillFile] = {} | |
return new_storage | |
def _remove_file(self): | |
self.output(os.linesep.join(self.storage)) | |
title = input("Remove file (file title)? ") | |
if title not in self.storage: | |
self.output("File not found.") | |
return | |
del self.storage[title] | |
self.output(f"Removed file: {title}") | |
if __name__ == '__main__': | |
# Example usage only. | |
storage_file_path = "storage.pickle" | |
if not os.path.exists(storage_file_path): | |
mem_storage = EditQuill.storage_factory() | |
else: | |
with open(storage_file_path, "rb") as storage_file: | |
mem_storage = pickle.load(storage_file) | |
editor = EditQuill(storage=mem_storage, allow_save=True, allow_load=True, allow_remove=True) | |
editor.run() | |
editor.output(os.linesep.join(editor.current_file.text_lines)) | |
with open(storage_file_path, "wb") as storage_file: | |
pickle.dump(editor.storage, storage_file) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment