Skip to content

Commit

Permalink
Enable custom TW filters + virtual tags - only available in tw_gtasks…
Browse files Browse the repository at this point in the history
…_sync atm
  • Loading branch information
bergercookie committed Jan 20, 2024
1 parent 04b39c5 commit 7bb42e3
Show file tree
Hide file tree
Showing 6 changed files with 393 additions and 438 deletions.
603 changes: 294 additions & 309 deletions poetry.lock

Large diffs are not rendered by default.

12 changes: 4 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ gkeepapi = { version = "^0.13.7", optional = true }
asana = { version = "^1.0.0", optional = true }
caldav = { version = "^0.11.0", optional = true }
icalendar = { version = "^5.0.3", optional = true }
taskw = { version = "^1.3.1", optional = true }
taskw-ng = { version = "0.1.1", optional = true }
# taskw-ng = { path = "/home/berger/src_/taskw-ng", develop = true }
xattr = { version = "^0.9.9", optional = true }
xdg = { version = "^6.0.0", optional = true }

Expand All @@ -87,7 +88,7 @@ gkeep = ["gkeepapi"]
notion = ["notion-client"]
asana = ["asana"]
caldav = ["caldav", "icalendar"]
tw = ["taskw", "xdg"]
tw = ["taskw-ng", "xdg"]
fs = ["xattr"]

# dev dependencies -------------------------------------------------------------
Expand All @@ -112,12 +113,9 @@ coveralls = "^3.3.1"
pycln = "^1.3.1"
check-jsonschema = "^0.14.3"
# readline = "6.2.4.1"

# isort ------------------------------------------------------------------------

[tool.poetry.group.dev.dependencies]
ruff = "^0.1.5"

# isort ------------------------------------------------------------------------
[tool.isort]
include_trailing_comma = true
line_length = 95
Expand All @@ -142,8 +140,6 @@ module = [
"pytest",
"pexpect",
"notion_client",
"taskw",
"taskw.warrior",
"google.auth.transport.requests",
"google_auth_oauthlib.flow",
]
Expand Down
59 changes: 30 additions & 29 deletions syncall/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,16 @@ def opt_list_asana_workspaces():
# taskwarrior options -------------------------------------------------------------------------
def opts_tw_filtering():
def decorator(f):
for d in reversed([
opt_tw_filter,
opt_tw_all_tasks,
opt_tw_tags,
opt_tw_project,
opt_tw_only_tasks_modified_30_days,
]):
for d in reversed(
[
opt_tw_filter,
opt_tw_all_tasks,
opt_tw_tags,
opt_tw_project,
opt_tw_only_tasks_modified_X_days,
opt_prefer_scheduled_date,
]
):
f = d()(f)
return f

Expand All @@ -105,7 +108,10 @@ def opt_tw_filter():
"--tw-filter",
"tw_filter",
type=str,
help="Taskwarrior filter for specifying the tasks to synchronize",
help=(
"Taskwarrior filter for specifying the tasks to synchronize. These filters will be"
" concatenated using OR with potential tags and projects potentially specified"
),
)


Expand All @@ -120,51 +126,46 @@ def opt_tw_all_tasks():


def opt_tw_tags():
def callback(ctx, param, value):
if value is None or ctx.resilient_parsing:
return

return " ".join(f"+{one_val}" for one_val in value)

return click.option(
"-t",
"--taskwarrior-tags",
"tw_tags",
type=str,
help="Taskwarrior tags to synchronize",
callback=callback,
expose_value=True,
multiple=True,
)


def opt_tw_project():
def callback(ctx, param, value):
if value is None or ctx.resilient_parsing:
return

return f"pro:{value}"

return click.option(
"-p",
"--tw-project",
"tw_project",
type=str,
help="Taskwarrior project to synchronize",
callback=callback,
expose_value=True,
is_eager=True,
)


# TODO
def opt_tw_only_tasks_modified_30_days():
def opt_tw_only_tasks_modified_X_days():
def callback(ctx, param, value):
if value is None or ctx.resilient_parsing:
return

return f"modified.after:-{value}d"

return click.option(
"--30-days",
"--days",
"--only-modified-last-X-days",
"tw_only_modified_last_30_days",
is_flag=True,
help="Only synchronize Taskwarrior tasks that have been modified in the last 30 days",
"tw_only_modified_last_X_days",
type=str,
help=(
"Only synchronize Taskwarrior tasks that have been modified in the last X days"
" (specify X, e.g., 1, 30, 0.5, etc.)"
),
callback=callback,
)


Expand All @@ -180,7 +181,7 @@ def opt_prefer_scheduled_date():
)


# notion
# notion --------------------------------------------------------------------------------------
def opt_notion_page_id():
return click.option(
"-n",
Expand Down
10 changes: 5 additions & 5 deletions syncall/scripts/tw_caldav_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
opt_caldav_user,
opt_pdb_on_error,
opt_tw_all_tasks,
opt_tw_only_tasks_modified_30_days,
opt_tw_only_tasks_modified_X_days,
)
from syncall.tw_caldav_utils import convert_caldav_to_tw, convert_tw_to_caldav

Expand Down Expand Up @@ -65,7 +65,7 @@
@opt_tw_all_tasks()
@opt_tw_tags()
@opt_tw_project()
@opt_tw_only_tasks_modified_30_days()
@opt_tw_only_tasks_modified_X_days()
# misc options --------------------------------------------------------------------------------
@opt_list_combinations("TW", "Caldav")
@opt_resolution_strategy()
Expand All @@ -83,7 +83,7 @@ def main(
tw_sync_all_tasks: bool,
tw_tags: List[str],
tw_project: str,
tw_only_modified_last_30_days: bool,
tw_only_modified_last_X_days: bool,
verbose: int,
combination_name: str,
custom_combination_savename: str,
Expand Down Expand Up @@ -148,7 +148,7 @@ def main(
"tw_project": tw_project,
"tw_tags": tw_tags,
"tw_sync_all_tasks": tw_sync_all_tasks,
"tw_only_modified_last_30_days": tw_only_modified_last_30_days,
"tw_only_modified_last_X_days": tw_only_modified_last_X_days,
},
config_fname="tw_caldav_configs",
custom_combination_savename=custom_combination_savename,
Expand All @@ -173,7 +173,7 @@ def main(
# TW
# TODO abstract this
only_modified_since = None
if tw_only_modified_last_30_days:
if tw_only_modified_last_X_days:
only_modified_since = datetime.datetime.now() - datetime.timedelta(days=30)

tw_side = TaskWarriorSide(
Expand Down
68 changes: 42 additions & 26 deletions syncall/scripts/tw_gtasks_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import click
from bubop import (
check_optional_mutually_exclusive,
check_required_mutually_exclusive,
format_dict,
log_to_syslog,
logger,
Expand Down Expand Up @@ -37,11 +38,8 @@
opt_gtasks_list,
opt_list_combinations,
opt_list_resolution_strategies,
opt_prefer_scheduled_date,
opt_pdb_on_error,
opt_resolution_strategy,
opt_tw_filter,
opt_tw_project,
opt_tw_tags,
opts_tw_filtering,
)

Expand All @@ -59,33 +57,32 @@
@opt_resolution_strategy()
@opt_combination("TW", "Google Tasks")
@opt_custom_combination_savename("TW", "Google Tasks")
@opt_prefer_scheduled_date()
@click.option("-v", "--verbose", count=True)
@click.version_option(__version__)
@opt_pdb_on_error()
def main(
gtasks_list: str,
google_secret: str,
oauth_port: int,

# tw
tw_filter: str,
tw_tags: List[str],
tw_project: str,
tw_only_modified_last_X_days: str,
tw_sync_all_tasks: bool,
tw_only_modified_last_30_days: bool,

prefer_scheduled_date: bool,
resolution_strategy: str,
verbose: int,
combination_name: str,
custom_combination_savename: str,
do_list_combinations: bool,
list_resolution_strategies: bool, # type: ignore
prefer_scheduled_date: bool,
pdb_on_error: bool,
):
"""Synchronize lists from your Google Tasks with filters from Taskwarrior.
The list of TW tasks is determined by a combination of TW tags and a TW project while the
list in GTasks should be provided by their name. if it doesn't exist it will be crated
The list of TW tasks can be based on a TW project, tag, on the modification date or on an
arbitrary filter while the list in GTasks should be provided by their name. if it doesn't
exist it will be created.
"""
# setup logger ----------------------------------------------------------------------------
loguru_tqdm_sink(verbosity=verbose)
Expand All @@ -98,23 +95,35 @@ def main(
return 0

# cli validation --------------------------------------------------------------------------
tw_filter_li = [
t
for t in [
tw_filter,
tw_only_modified_last_X_days,
]
if t
]

check_optional_mutually_exclusive(combination_name, custom_combination_savename)
combination_of_tw_project_tags_and_gtasks_list = any(
combination_of_tw_filter_and_gtasks_list = any(
[
tw_project,
tw_filter_li,
tw_tags,
tw_project,
tw_sync_all_tasks,
gtasks_list,
]
)
check_optional_mutually_exclusive(
combination_name, combination_of_tw_project_tags_and_gtasks_list
combination_name, combination_of_tw_filter_and_gtasks_list
)

# existing combination name is provided ---------------------------------------------------
if combination_name is not None:
app_config = fetch_app_configuration(
config_fname="tw_gtasks_configs", combination=combination_name
)
tw_filter_li = app_config["tw_filter_li"]
tw_tags = app_config["tw_tags"]
tw_project = app_config["tw_project"]
gtasks_list = app_config["gtasks_list"]
Expand All @@ -125,23 +134,23 @@ def main(
combination_name = cache_or_reuse_cached_combination(
config_args={
"gtasks_list": gtasks_list,
"tw_project": tw_project,
"tw_filter_li": tw_filter_li,
"tw_tags": tw_tags,
"tw_project": tw_project,
},
config_fname="tw_gtasks_configs",
custom_combination_savename=custom_combination_savename,
)

# at least one of tw_tags, tw_project should be set ---------------------------------------
if not tw_tags and not tw_project:
logger.error(
"You have to provide at least one valid tag or a valid project ID to use for the"
" synchronization. You can do so either via CLI arguments or by specifying an"
" existing saved combination"
)
sys.exit(1)

# more checks -----------------------------------------------------------------------------
combination_of_tw_related_options = any([tw_filter_li, tw_tags, tw_project])
check_required_mutually_exclusive(
tw_sync_all_tasks,
combination_of_tw_related_options,
"sync_all_tw_tasks",
"combination of specific TW-related options",
)

if gtasks_list is None:
logger.error(
"You have to provide the name of a Google Tasks list to synchronize events"
Expand All @@ -155,8 +164,10 @@ def main(
format_dict(
header="Configuration",
items={
"TW Filter": " ".join(tw_filter_li),
"TW Tags": tw_tags,
"TW Project": tw_project,
"TW Sync All Tasks": tw_sync_all_tasks,
"Google Tasks": gtasks_list,
"Prefer scheduled dates": prefer_scheduled_date,
},
Expand All @@ -166,7 +177,12 @@ def main(
)

# initialize sides ------------------------------------------------------------------------
tw_side = TaskWarriorSide(tags=tw_tags, project=tw_project)
# NOTE: We don't explicitly pass the tw_sync_all_tasks boolean. However we implicitly do by
# verifying beforehand that if this flag is specified the user cannot specify any of the
# other `tw_filter_li`, `tw_tags`, `tw_project` options.
tw_side = TaskWarriorSide(
tw_filter=" ".join(tw_filter_li), tags=tw_tags, project=tw_project
)

gtask_side = GTasksSide(
task_list_title=gtasks_list, oauth_port=oauth_port, client_secret=google_secret
Expand Down
Loading

0 comments on commit 7bb42e3

Please sign in to comment.