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

Pass arguments to the default command #238

Open
wants to merge 2 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
10 changes: 10 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,19 @@ releases, in reverse chronological order.
v3.2.0
------

New features
~~~~~~~~~~~~

* Allow passing arguments to the shortcut ``todo``, so ``todo --startable`` now
works as expected.
* Completing recurring todos now works as expected and does not make if
dissapear forever.

Packaging changes
~~~~~~~~~~~~~~~~~

* New runtime dependency: ``click-default-group``.

v3.1.0
------

Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
atomicwrites
click>=6.0
click_log>=0.1.3
click-default-group
configobj
humanize
icalendar
Expand Down
115 changes: 63 additions & 52 deletions todoman/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import click
import click_log
from click_default_group import DefaultGroup

from todoman import exceptions, formatters
from todoman.configuration import ConfigurationException, load_config
Expand Down Expand Up @@ -119,12 +120,6 @@ def _sort_callback(ctx, param, val):


def validate_status(ctx=None, param=None, val=None):
# The default command doesn't run callbacks as expected, so it needs to
# specify the callback'd type. When `list` is called explicitly, this
# callback *IS* run, so we need to handle that edge case:
if not isinstance(val, str):
return val

statuses = val.upper().split(',')

if 'ANY' in statuses:
Expand Down Expand Up @@ -162,10 +157,17 @@ def command_wrap(*a, **kw):

class AppContext:
def __init__(self):
self.config = None
self.db = None
self.formatter_class = None

self.init_config()

def init_config(self):
try:
self.config = load_config()
except ConfigurationException as e:
raise click.ClickException(e.args[0])

@cached_property
def ui_formatter(self):
return formatters.DefaultFormatter(
Expand All @@ -191,7 +193,56 @@ def formatter(self):
help='Go into interactive mode before saving the task.')


@click.group(invoke_without_command=True)
class ConfigurableDefaultGroup(DefaultGroup):

def get_command(self, ctx, cmd_name):
if cmd_name not in self.commands:
ctx = ctx.ensure_object(AppContext)
cmd_name = ctx.config['main']['default_command']
return super(DefaultGroup, self).get_command(ctx, cmd_name)


class Cli(ConfigurableDefaultGroup):

def __init__(self, *a, **kw):
super().__init__(*a, **kw, default='default', default_if_no_args=True)

def callback(click_ctx, color, porcelain, humanize):
ctx = click_ctx.ensure_object(AppContext)

if porcelain and humanize:
raise click.ClickException('--porcelain and --humanize cannot be used'
' at the same time.')

if humanize is None: # False means explicitly disabled
humanize = ctx.config['main']['humanize']

if humanize:
ctx.formatter_class = formatters.HumanizedFormatter
elif porcelain:
ctx.formatter_class = formatters.PorcelainFormatter
else:
ctx.formatter_class = formatters.DefaultFormatter

color = color or ctx.config['main']['color']
if color == 'always':
click_ctx.color = True
elif color == 'never':
click_ctx.color = False

paths = [
path for path in glob.iglob(expanduser(ctx.config["main"]["path"]))
if isdir(path)
]
if len(paths) == 0:
raise exceptions.NoListsFound(ctx.config["main"]["path"])

ctx.db = Database(paths, ctx.config['main']['cache_path'])

# Make python actually use LC_TIME, or the user's locale settings
locale.setlocale(locale.LC_TIME, "")


@click_log.init('todoman')
@click_log.simple_verbosity_option()
@click.option('--colour', '--color', default=None,
Expand All @@ -207,50 +258,10 @@ def formatter(self):
@click.pass_context
@click.version_option(prog_name='todoman')
@catch_errors
def cli(click_ctx, color, porcelain, humanize):
ctx = click_ctx.ensure_object(AppContext)
try:
ctx.config = load_config()
except ConfigurationException as e:
raise click.ClickException(e.args[0])

if porcelain and humanize:
raise click.ClickException('--porcelain and --humanize cannot be used'
' at the same time.')
def get_cli():
return Cli()

if humanize is None: # False means explicitly disabled
humanize = ctx.config['main']['humanize']

if humanize:
ctx.formatter_class = formatters.HumanizedFormatter
elif porcelain:
ctx.formatter_class = formatters.PorcelainFormatter
else:
ctx.formatter_class = formatters.DefaultFormatter

color = color or ctx.config['main']['color']
if color == 'always':
click_ctx.color = True
elif color == 'never':
click_ctx.color = False

paths = [
path for path in glob.iglob(expanduser(ctx.config["main"]["path"]))
if isdir(path)
]
if len(paths) == 0:
raise exceptions.NoListsFound(ctx.config["main"]["path"])

ctx.db = Database(paths, ctx.config['main']['cache_path'])

# Make python actually use LC_TIME, or the user's locale settings
locale.setlocale(locale.LC_TIME, "")

if not click_ctx.invoked_subcommand:
invoke_command(
click_ctx,
ctx.config['main']['default_command'],
)
cli = get_cli()


def invoke_command(click_ctx, command):
Expand Down Expand Up @@ -483,7 +494,7 @@ def move(ctx, list, ids):
callback=_validate_startable_param, help='Show only todos which '
'should can be started today (i.e.: start time is not in the '
'future).')
@click.option('--status', '-s', default=['NEEDS-ACTION', 'IN-PROCESS'],
@click.option('--status', '-s', default='NEEDS-ACTION,IN-PROCESS',
callback=validate_status, help='Show only todos with the '
'provided comma-separated statuses. Valid statuses are '
'"NEEDS-ACTION", "CANCELLED", "COMPLETED", "IN-PROCESS" or "ANY"'
Expand Down