From 06f47640afb16f00b2f9821d2d7f9c3564f7a306 Mon Sep 17 00:00:00 2001 From: Corentin Girard Date: Sat, 5 Jun 2021 16:28:55 +0200 Subject: [PATCH] Support notes with checkboxes --- .gitignore | 1 + .vscode/launch.json | 32 ++++++++++++++ chardetect | 33 ++++++++++++++ futurize | 33 ++++++++++++++ gkeep | 33 ++++++++++++++ gkeep.egg-link | 2 + google_keep_tasks/notes.py | 89 ++++++++++++++++++++++++++++++++++++-- pasteurize | 33 ++++++++++++++ 8 files changed, 252 insertions(+), 4 deletions(-) create mode 100644 .vscode/launch.json create mode 100755 chardetect create mode 100755 futurize create mode 100755 gkeep create mode 100644 gkeep.egg-link create mode 100755 pasteurize diff --git a/.gitignore b/.gitignore index f0d7231..a54b13e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ __pycache__ /dist /*.egg-info +build \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6af608b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,32 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Debug", + "type": "python", + "request": "launch", + "program": "./gkeep", + "args": [ + "notes", + + // "get", + // "1622667139340.93561477" + + "edit", + "--filter-id", + "1622667139340.93561477", + "--text" + + // "edit", + // "--filter-title", + // "TODOloulou", + // "--text", + // "coucou" + ], + "console": "integratedTerminal" + } + ] +} \ No newline at end of file diff --git a/chardetect b/chardetect new file mode 100755 index 0000000..fb99f6b --- /dev/null +++ b/chardetect @@ -0,0 +1,33 @@ +#!/usr/bin/python +# EASY-INSTALL-ENTRY-SCRIPT: 'chardet','console_scripts','chardetect' +import re +import sys + +# for compatibility with easy_install; see #2198 +__requires__ = 'chardet' + +try: + from importlib.metadata import distribution +except ImportError: + try: + from importlib_metadata import distribution + except ImportError: + from pkg_resources import load_entry_point + + +def importlib_load_entry_point(spec, group, name): + dist_name, _, _ = spec.partition('==') + matches = ( + entry_point + for entry_point in distribution(dist_name).entry_points + if entry_point.group == group and entry_point.name == name + ) + return next(matches).load() + + +globals().setdefault('load_entry_point', importlib_load_entry_point) + + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(load_entry_point('chardet', 'console_scripts', 'chardetect')()) diff --git a/futurize b/futurize new file mode 100755 index 0000000..28ddd68 --- /dev/null +++ b/futurize @@ -0,0 +1,33 @@ +#!/usr/bin/python +# EASY-INSTALL-ENTRY-SCRIPT: 'future','console_scripts','futurize' +import re +import sys + +# for compatibility with easy_install; see #2198 +__requires__ = 'future' + +try: + from importlib.metadata import distribution +except ImportError: + try: + from importlib_metadata import distribution + except ImportError: + from pkg_resources import load_entry_point + + +def importlib_load_entry_point(spec, group, name): + dist_name, _, _ = spec.partition('==') + matches = ( + entry_point + for entry_point in distribution(dist_name).entry_points + if entry_point.group == group and entry_point.name == name + ) + return next(matches).load() + + +globals().setdefault('load_entry_point', importlib_load_entry_point) + + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(load_entry_point('future', 'console_scripts', 'futurize')()) diff --git a/gkeep b/gkeep new file mode 100755 index 0000000..f9589b3 --- /dev/null +++ b/gkeep @@ -0,0 +1,33 @@ +#!/usr/bin/python +# EASY-INSTALL-ENTRY-SCRIPT: 'gkeep','console_scripts','gkeep' +import re +import sys + +# for compatibility with easy_install; see #2198 +__requires__ = 'gkeep' + +try: + from importlib.metadata import distribution +except ImportError: + try: + from importlib_metadata import distribution + except ImportError: + from pkg_resources import load_entry_point + + +def importlib_load_entry_point(spec, group, name): + dist_name, _, _ = spec.partition('==') + matches = ( + entry_point + for entry_point in distribution(dist_name).entry_points + if entry_point.group == group and entry_point.name == name + ) + return next(matches).load() + + +globals().setdefault('load_entry_point', importlib_load_entry_point) + + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(load_entry_point('gkeep', 'console_scripts', 'gkeep')()) diff --git a/gkeep.egg-link b/gkeep.egg-link new file mode 100644 index 0000000..559c5f0 --- /dev/null +++ b/gkeep.egg-link @@ -0,0 +1,2 @@ +/home/corentin/playground/gkeep +. \ No newline at end of file diff --git a/google_keep_tasks/notes.py b/google_keep_tasks/notes.py index 7ec5689..dc0a67e 100644 --- a/google_keep_tasks/notes.py +++ b/google_keep_tasks/notes.py @@ -2,6 +2,9 @@ import click import gkeepapi import sys +import re + +from gkeepapi.node import List from google_keep_tasks.cli import GkeepGroup from google_keep_tasks.exceptions import InvalidColor @@ -73,6 +76,21 @@ def query_params(keep, **kwargs): set(x.labels.all()) == set(labels) if labels is not None else None])) return kwargs +def format_note_item(item): + return u'%s%s %s' % ( + ' ' if item.indented else '', + u'- [x]' if item.checked else u'- [ ]', + item.text + ) + +def format_note(note): + if not isinstance(note, List): + return note.text + text = "" + for item in note.items: + text += "%s | %s\n" % (item.id.ljust(30), format_note_item(item)) + return text + def print_note(note): params = COLORS.get(note.color, {}) @@ -82,15 +100,74 @@ def print_note(note): click.echo(click.style('"' * len(note_id), **params)) if note.title: click.echo(click.style(note.title, bold=True)) - click.echo(note.text) + click.echo('-' * len(note_id)) + click.echo(format_note(note)) + click.echo('-' * len(note_id)) if note.labels: - click.echo() click.echo(' '.join(click.style('[{}]'.format(label.name), underline=True, bold=True) for label in note.labels.all())) click.echo(click.style('"' * len(note_id), **params)) click.echo('\n') +def edit_note_checkboxes(note): + text = click.edit(format_note(note)).strip() + lines = text.split("\n") + regex = re.compile(r"([\w.]*) *\| ( *)- \[(x| )\] (.*)") + + last_old_note = 'top' + current_items = [] + for line in lines: + id, indent, check_mark, content = regex.match(line).groups() + found = list(filter(lambda item: item.id == id, note.items)) + old_note = found[0] if len(found) > 0 else None + indented = len(indent) > 1 + checked = check_mark == 'x' + current_items.append({ + 'id': id, + 'previous': last_old_note, + 'old': old_note, + 'indented': indented, + 'checked': checked, + 'content': content + }) + last_old_note = old_note + + # Deletion + for item in note.items: + if item.id not in [parts['id'] for parts in current_items]: + item.delete() + + last_added = None + for parts in current_items: + previous = parts['previous'] if parts['previous'] != None else last_added + + # Addition + if parts['old'] == None: + sort = int(previous.sort) - 1 if previous != None and previous != 'top' else gkeepapi.node.NewListItemPlacementValue.Top + added = note.add(parts['content'], parts['checked'], sort) + if parts['indented']: + previous.indent(added) + last_added = added + + # Modification + else: + if parts['old'].indented and not parts['indented']: + if previous != None: + previous.dedent(parts['old']) + if not parts['old'].indented and parts['indented']: + if previous != None: + previous.indent(parts['old']) + + if parts['old'].checked and not parts['checked']: + parts['old'].checked = False + if not parts['old'].checked and parts['checked']: + parts['old'].checked = True + + if parts['old'].text != parts['content']: + parts['old'].text = parts['content'] + + def get_note_instance(keep, id=None, **kwargs): if id: note = keep.get(id) @@ -234,7 +311,11 @@ def edit_note(ctx, title, text, color, labels, archived, pinned, filter_id, filt click.echo('The note was not found', err=True) sys.exit(2) if text == placeholder: - text = click.edit(note.text).strip() + if isinstance(note, List): + edit_note_checkboxes(note) + text = None + else: + text = click.edit(note.text).strip() updated = {} boolean_nullables = ['archived', 'pinned'] # 3 state params for param in ['title', 'text', 'color', 'labels'] + boolean_nullables: @@ -275,4 +356,4 @@ def delete_note(ctx, **kwargs): click.echo('Note with title "{}" deleted.'.format(note.title)) else: click.echo('The note was not found', err=True) - sys.exit(2) + sys.exit(2) \ No newline at end of file diff --git a/pasteurize b/pasteurize new file mode 100755 index 0000000..c673c43 --- /dev/null +++ b/pasteurize @@ -0,0 +1,33 @@ +#!/usr/bin/python +# EASY-INSTALL-ENTRY-SCRIPT: 'future','console_scripts','pasteurize' +import re +import sys + +# for compatibility with easy_install; see #2198 +__requires__ = 'future' + +try: + from importlib.metadata import distribution +except ImportError: + try: + from importlib_metadata import distribution + except ImportError: + from pkg_resources import load_entry_point + + +def importlib_load_entry_point(spec, group, name): + dist_name, _, _ = spec.partition('==') + matches = ( + entry_point + for entry_point in distribution(dist_name).entry_points + if entry_point.group == group and entry_point.name == name + ) + return next(matches).load() + + +globals().setdefault('load_entry_point', importlib_load_entry_point) + + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(load_entry_point('future', 'console_scripts', 'pasteurize')())