Skip to content

Instantly share code, notes, and snippets.

@TimelessP
Last active February 5, 2022 19:30
Show Gist options
  • Save TimelessP/c6b0c20b314f6213ced329286fa6d4bf to your computer and use it in GitHub Desktop.
Save TimelessP/c6b0c20b314f6213ced329286fa6d4bf to your computer and use it in GitHub Desktop.
editquill - a super-minimal, line-mode, sandboxed text editor utility
#!/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