Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interactive mode refactoring #33

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions exceptions/export_directory_not_found_exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class ExportDirectoryNotFound(Exception):
def __init__(self, message="Export directory not found"):
self.message = message
super().__init__(self.message)

5 changes: 5 additions & 0 deletions exceptions/export_format_not_recognized_exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class ExportFormatNotRecognized(Exception):
def __init__(self, message="Export format not recognized"):
self.message = message
super().__init__(self.message)

5 changes: 5 additions & 0 deletions exceptions/sqlite_file_not_found_exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class SqliteFileNotFoundException(Exception):
def __init__(self, message="sqlite file not found"):
self.message = message
super().__init__(self.message)

2 changes: 2 additions & 0 deletions exceptions/user_asks_for_end_of_interactive_mode_exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class UserAsksforEndOfInteractiveModeException(Exception):
pass
43 changes: 41 additions & 2 deletions kobo-annotations-exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,22 @@
from services.argument_checker import check_date
from services.argument_checker import check_file_existence
from services.argument_checker import check_folder_existence
from services.interactive_prompter import InteractivePrompter
from exceptions.user_asks_for_end_of_interactive_mode_exception import UserAsksforEndOfInteractiveModeException
from services.annotation_retriever import AnnotationRetriever
from services.exporter_interface import ExporterInterface
from services.word_exporter import WordExporter
from services.text_exporter import TextExporter
from services.console_exporter import ConsoleExporter


def instanciate_exporter(export_type: str) -> ExporterInterface:
if "word" == export_type:
return WordExporter()
if "text" == export_type:
return TextExporter()

return ConsoleExporter()


def main() -> None:
Expand All @@ -11,7 +27,12 @@ def main() -> None:
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)

parser.add_argument('--interactive',
'-i',
action='store_true',
help='Interactive mode')
parser.add_argument('sqlite',
action='store_true',
type=check_file_existence,
help="Define the sqlite file location")
parser.add_argument('--format',
Expand All @@ -31,9 +52,27 @@ def main() -> None:
help="Export updated annotations since a given date (format: YYYY-MM-DD HH:MM:SS", )

args = parser.parse_args()
print(args)

if not args.interactive:
if args.sqlite is None or args.format is None or args.directory is None:
parser.error('without -2, *both* -3 <a> *and* -4 <b> are required')

annotation_handler = AnnotationHandler(args.sqlite, args.format, args.directory, args.since)
annotation_handler.handle()
try:
if args.interactive:
interactive_prompter = InteractivePrompter()
interactive_prompter.ask_information()
retriever = AnnotationRetriever(interactive_prompter.sqlite)
annotations = retriever.retrieve(args.since)
filtered_annotations = interactive_prompter.ask_books(annotations)
exporter = instanciate_exporter(interactive_prompter.format)
exporter.export(filtered_annotations, interactive_prompter.directory)
else:
annotation_handler = AnnotationHandler(args.sqlite, args.format, args.directory, args.since)
annotation_handler.handle()
except UserAsksforEndOfInteractiveModeException:
print("Bye.")
exit()


if __name__ == "__main__":
Expand Down
3 changes: 1 addition & 2 deletions services/annotation_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ def __init__(self,
self.export_since = export_since

def handle(self) -> None:
exporter = instanciate_exporter(self.export_format)
retriever = AnnotationRetriever(self.sqlite_file_name)

annotations = retriever.retrieve(self.export_since)
exporter = instanciate_exporter(self.export_format)
exporter.export(annotations, self.export_directory)
107 changes: 107 additions & 0 deletions services/interactive_prompter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
from exceptions.user_asks_for_end_of_interactive_mode_exception import UserAsksforEndOfInteractiveModeException
from exceptions.sqlite_file_not_found_exception import SqliteFileNotFoundException
from exceptions.export_format_not_recognized_exception import ExportFormatNotRecognized
from exceptions.export_directory_not_found_exception import ExportDirectoryNotFound
import os.path


class InteractivePrompter:
HOW_TO_QUIT = "You can exit this interactive mode by tiping 'quit'"
QUIT = "quit"
AVAILABLE_EXPORT_FORMATS = ["console", "word", "text"]

def __init__(self) -> None:
self.books_selection = []
self.end_of_prompt = False
self.sqlite = ""
self.format = "console"
self.directory = "exports"

def ask_books(self, annotations: dict) -> dict:
titles = {}
i = 1
for author in annotations:
books = annotations[author]
for book in books:
titles[i] = {
'author': author,
'title': book}
print("{0} : {1} - {2}".format(i, book, author))
i = i + 1

self.__prompt_books_selection()

filtered_annotation = {}

for indice in titles:
if indice in self.books_selection:
selected_book = titles[indice].get('title')
selected_author = titles[indice].get('author')
if selected_author not in filtered_annotation:
filtered_annotation[selected_author] = {}
if selected_book not in filtered_annotation[selected_author]:
filtered_annotation[selected_author][selected_book] = {}
filtered_annotation[selected_author][selected_book] = annotations[selected_author].get(selected_book)

return filtered_annotation

def ask_information(self):
print("###############################################################################")
print("# #")
print("# Kobo Annotations Exporter #")
print("# #")
print("###############################################################################")
print("Welcome to the Kobo Annotations Exporter !")
print("This software will help you to export annotations you made during your reading on your beloved Kobo "
"device.")

while not self.end_of_prompt:
try:
# self.__prompt_sqlite_location()
# TODO: DEBUG
self.sqlite = "databases/database.sqlite"
self.__prompt_export_format()
self.__prompt_export_directory_location()
self.__set_end_of_prompt()
except (SqliteFileNotFoundException, ExportFormatNotRecognized, ExportDirectoryNotFound) as e:
print(e.message)

def __prompt_books_selection(self) -> None:
raw_books_selection = self.__prompt_text_or_quit(
"Which books do you want to export ? Select books by typing their number separated by space. Press 'Enter' for exporting all")
self.books_selection = [int(x) for x in raw_books_selection.split(" ")]

def __prompt_sqlite_location(self) -> None:
raw_sqlite = self.__prompt_text_or_quit("First of all, where is your Kobo sqlite file ?")
if not os.path.isfile(raw_sqlite):
raise SqliteFileNotFoundException("file '{}' not found".format(raw_sqlite))
self.sqlite = raw_sqlite

def __prompt_export_format(self) -> None:
raw_export_format = self.__prompt_text_or_quit(
"In which format do you want to export (word or text) ? Press 'Enter' for console export")
if not raw_export_format == '' and raw_export_format not in self.AVAILABLE_EXPORT_FORMATS:
raise ExportFormatNotRecognized(
"export format '{0}' not found in {1}".format(raw_export_format, self.AVAILABLE_EXPORT_FORMATS))
self.format = raw_export_format

def __prompt_export_directory_location(self) -> None:
raw_export_directory = self.__prompt_text_or_quit(
"In which directory do you want to export your annotations ? Press 'Enter' for the default directory "
"'exports'")
if raw_export_directory == '':
raw_export_directory = "exports"
if not os.path.isdir(raw_export_directory):
os.mkdir(raw_export_directory)
elif not os.path.isdir(raw_export_directory):
raise ExportDirectoryNotFound("directory '{0}' not found".format(raw_export_directory))
self.directory = raw_export_directory if raw_export_directory[-1] != '/' else raw_export_directory[:-1]

def __prompt_text_or_quit(self, question: str) -> str:
answer = input("{0} ({1}): ".format(question, self.HOW_TO_QUIT))
if self.QUIT == answer:
raise UserAsksforEndOfInteractiveModeException
return answer

def __set_end_of_prompt(self) -> None:
self.end_of_prompt = True