Skip to content

Commit

Permalink
Merge pull request #1308 from pimutils/feature/json_export
Browse files Browse the repository at this point in the history
Support json as an output option for most commands
  • Loading branch information
geier authored Oct 29, 2023
2 parents ae33ad3 + 1d60f78 commit 4d4fb9b
Show file tree
Hide file tree
Showing 12 changed files with 375 additions and 102 deletions.
1 change: 1 addition & 0 deletions AUTHORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,4 @@ Raúl Medina - raulmgcontact [at] gmail (dot] com
Matthew Rademaker - matthew.rademaker [at] gmail [dot] com
Valentin Iovene - val [at] too [dot] gy
Julian Wollrath
Mattori Birnbaum - me [at] mattori [dot] com - https://mattori.com
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ not released yet
* NEW properties of ikhal themes (dark and light) can now be overriden from the
config file (via the new [palette] section, check the documenation)
* NEW timedelta strings can now have a leading `+`, e.g. `+1d`
* NEW Add `--json` option to output event data as JSON objects

0.11.2
======
Expand Down
57 changes: 53 additions & 4 deletions doc/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,21 @@ Several options are common to almost all of :program:`khal`'s commands
url-separator
A separator: " :: " that appears when there is a url.

duration
The duration of the event in terms of days, hours, months, and seconds
(abbreviated to `d`, `h`, `m`, and `s` respectively).

repeat-pattern
The raw iCal recurrence rule if the event is repeating.

all-day
A boolean indicating whether it is an all-day event or not.

categories
The categories of the event.

By default, all-day events have no times. To see a start and end time anyway simply
add `-full` to the end of any template with start/end, for instance
add `-full` to the end of any template with start/end or duration, for instance
`start-time` becomes `start-time-full` and will always show start and end times (instead
of being empty for all-day events).

Expand All @@ -191,6 +204,40 @@ Several options are common to almost all of :program:`khal`'s commands
khal list --format "{title} {description}"


.. option:: --json FIELD ...

Works similar to :option:`--format`, but instead of defining a format string a JSON
object is created for each specified field. The matching events are collected into
a JSON array. This option accepts the following subset of :option:`--format`
template options

::

title, description, uid, start, start-long, start-date,
start-date-long, start-time, end, end-long, end-date,
end-date-long, end-time, start-full, start-long-full,
start-date-full, start-date-long-full, start-time-full,
end-full, end-long-full, end-date-full, end-date-long-full,
end-time-full, repeat-symbol, location, calendar,
calendar-color, start-style, to-style, end-style,
start-end-time-style, end-necessary, end-necessary-long,
status, cancelled, organizer, url, duration, duration-full,
repeat-pattern, all-day, categories


Note that `calendar-color` will be the actual color name rather than the ANSI color code,
and the `repeat-symbol`, `status`, and `cancelled` values will have leading/trailing
whitespace stripped. Additionally, if only the special value `all` is specified then
all fields will be enabled.

Below is an example command which prints a JSON list of objects containing the title and
description of all events today.

::

khal list --json title --json description


.. option:: --day-format DAYFORMAT

works similar to :option:`--format`, but for day headings. It only has a few
Expand Down Expand Up @@ -231,8 +278,9 @@ shows all events scheduled for a given date (or datetime) range, with custom
formatting:
::

khal list [-a CALENDAR ... | -d CALENDAR ...] [--format FORMAT]
[--day-format DAYFORMAT] [--once] [--notstarted] [START [END | DELTA] ]
khal list [-a CALENDAR ... | -d CALENDAR ...]
[--format FORMAT] [--json FIELD ...] [--day-format DAYFORMAT]
[--once] [--notstarted] [START [END | DELTA] ]

START and END can both be given as dates, datetimes or times (it is assumed
today is meant in the case of only a given time) in the formats configured in
Expand Down Expand Up @@ -270,7 +318,8 @@ start.

::

khal at [-a CALENDAR ... | -d CALENDAR ...] [--format FORMAT]
khal at [-a CALENDAR ... | -d CALENDAR ...]
[--format FORMAT] [--json FIELD ...]
[--notstarted] [[START DATE] TIME | now]

calendar
Expand Down
27 changes: 20 additions & 7 deletions khal/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from .exceptions import FatalError
from .settings import InvalidSettingsError, NoConfigFile, get_config
from .terminal import colored
from .utils import human_formatter, json_formatter

try:
from setproctitle import setproctitle
Expand Down Expand Up @@ -313,11 +314,12 @@ def calendar(ctx, include_calendar, exclude_calendar, daterange, once,
)
@click.option('--notstarted', help=('Print only events that have not started.'),
is_flag=True)
@click.option('--json', help=("Fields to output in json"), multiple=True)
@click.argument('DATERANGE', nargs=-1, required=False,
metavar='[DATETIME [DATETIME | RANGE]]')
@click.pass_context
def klist(ctx, include_calendar, exclude_calendar,
daterange, once, notstarted, format, day_format):
daterange, once, notstarted, json, format, day_format):
"""List all events between a start (default: today) and (optional)
end datetime."""
try:
Expand All @@ -332,7 +334,8 @@ def klist(ctx, include_calendar, exclude_calendar,
once=once,
notstarted=notstarted,
conf=ctx.obj['conf'],
env={"calendars": ctx.obj['conf']['calendars']}
env={"calendars": ctx.obj['conf']['calendars']},
json=json
)
if event_column:
click.echo('\n'.join(event_column))
Expand All @@ -358,14 +361,15 @@ def klist(ctx, include_calendar, exclude_calendar,
help=('Stop an event repeating on this date.'))
@click.option('--format', '-f',
help=('The format to print the event.'))
@click.option('--json', help=("Fields to output in json"), multiple=True)
@click.option('--alarms', '-m',
help=('Alarm times for the new event as DELTAs comma separated'))
@click.option('--url', help=("URI for the event."))
@click.argument('info', metavar='[START [END | DELTA] [TIMEZONE] [SUMMARY] [:: DESCRIPTION]]',
nargs=-1)
@click.pass_context
def new(ctx, calendar, info, location, categories, repeat, until, alarms, url, format,
interactive):
json, interactive):
'''Create a new event from arguments.
START and END can be either dates, times or datetimes, please have a
Expand Down Expand Up @@ -413,6 +417,7 @@ def new(ctx, calendar, info, location, categories, repeat, until, alarms, url, f
alarms=alarms,
url=url,
format=format,
json=json
)
except FatalError as error:
logger.debug(error, exc_info=True)
Expand Down Expand Up @@ -583,9 +588,10 @@ def printics(ctx, ics, format):
@multi_calendar_option
@click.option('--format', '-f',
help=('The format of the events.'))
@click.option('--json', help=("Fields to output in json"), multiple=True)
@click.argument('search_string')
@click.pass_context
def search(ctx, format, search_string, include_calendar, exclude_calendar):
def search(ctx, format, json, search_string, include_calendar, exclude_calendar):
'''Search for events matching SEARCH_STRING.
For recurring events, only the master event and different overwritten
Expand All @@ -604,8 +610,13 @@ def search(ctx, format, search_string, include_calendar, exclude_calendar):
term_width, _ = get_terminal_size()
now = dt.datetime.now()
env = {"calendars": ctx.obj['conf']['calendars']}
if len(json) == 0:
formatter = human_formatter(format)
else:
formatter = json_formatter(json)
for event in events:
desc = textwrap.wrap(event.format(format, relative_to=now, env=env), term_width)
desc = textwrap.wrap(formatter(
event.attributes(relative_to=now, env=env)), term_width)
event_column.extend(
[colored(d, event.color,
bold_for_light_color=ctx.obj['conf']['view']['bold_for_light_color'])
Expand Down Expand Up @@ -655,9 +666,10 @@ def edit(ctx, format, search_string, show_past, include_calendar, exclude_calend
help=('The format of the day line.'))
@click.option('--notstarted', help=('Print only events that have not started'),
is_flag=True)
@click.option('--json', help=("Fields to output in json"), multiple=True)
@click.argument('DATETIME', nargs=-1, required=False, metavar='[[START DATE] TIME | now]')
@click.pass_context
def at(ctx, datetime, notstarted, format, day_format, include_calendar, exclude_calendar):
def at(ctx, datetime, notstarted, format, day_format, json, include_calendar, exclude_calendar):
'''Print all events at a specific datetime (defaults to now).'''
if not datetime:
datetime = ("now",)
Expand All @@ -675,7 +687,8 @@ def at(ctx, datetime, notstarted, format, day_format, include_calendar, exclude_
once=True,
notstarted=notstarted,
conf=ctx.obj['conf'],
env={"calendars": ctx.obj['conf']['calendars']}
env={"calendars": ctx.obj['conf']['calendars']},
json=json
)
if rows:
click.echo('\n'.join(rows))
Expand Down
Loading

0 comments on commit 4d4fb9b

Please sign in to comment.