Add ability to generate a meeting index

Adding optional parameters -t and -w used to generate an index of
meetings using a Jinja2 template.

Refactor output file/dir preparation so that index generation can make
use of it.

Expose UTC time in the Schedule object and human-friendly description of
the recurrence in the Recurrence objects, so that they are directly
usable in the templates.

Change-Id: I103ac552f43a02a0b6e2e3ad6e5f52a6877efa68
This commit is contained in:
Thierry Carrez 2015-02-27 16:01:12 +01:00
parent f14a9b4736
commit 8bc6cf87c5
7 changed files with 125 additions and 40 deletions

18
meetings/example.jinja Normal file
View File

@ -0,0 +1,18 @@
<h1>IRC meetings</h1>
{% for meeting in meetings %}
<h3>{{ meeting.project }}</h3>
<ul>
{% for schedule in meeting.schedules %}
<li>{{ schedule.recurrence }} on {{ schedule.day }} at
<a href="http://www.timeanddate.com/worldclock/fixedtime.html?hour={{ schedule.utc[:2] }}&min={{ schedule.utc[2:] }}&sec=0">{{ schedule.utc }} UTC</a>
in #{{ schedule.irc }}</li>
{% endfor %}
</ul>
Chair (to contact for more information): {{ meeting.chair }}</p>
{{ meeting.description|urlize }}
{% endfor %}

View File

@ -1,4 +1,5 @@
pbr>=0.6,!=0.7,<1.0
argparse
icalendar
jinja2
pyyaml

View File

@ -15,6 +15,8 @@ import logging
import os
from yaml2ical import ical
from yaml2ical import index
from yaml2ical import meeting
# logging settings
@ -45,48 +47,79 @@ project infrastructure.
outputtype.add_argument("-o", "--output",
dest="icalfile",
help="output file (one file for all meetings)")
parser.add_argument("-t", "--indextemplate",
dest="index_template",
help="generate an index from selected meetings")
parser.add_argument("-w", "--indexoutput",
dest="index_output",
help="output index file")
parser.add_argument("-f", "--force",
dest="force",
action='store_true',
help="forcefully remove/overwrite previous .ics "
"output files")
help="remove/overwrite previous output files")
# parse arguments:
return parser.parse_args()
args = parser.parse_args()
if ((args.index_template and not args.index_output) or
(args.index_output and not args.index_template)):
parser.error("You need to provide both -t and "
"-w if you want to output an index.")
return args
def _check_if_location_exists(location):
if not os.path.isdir(location):
raise ValueError("Invalid location %s" % location)
def _check_if_location_exists(location, style='f'):
if style == 'd' and not os.path.isdir(location):
raise ValueError("Directory %s does not exist" % location)
if style == 'f' and not os.path.isfile(location):
raise ValueError("File %s does not exist" % location)
def _prepare_output(output, style='f', force=False):
location = os.path.abspath(output)
if style == 'd':
_check_if_location_exists(location, style=style)
if os.listdir(location) != []:
if force:
for f in os.listdir(location):
file_path = os.path.join(location, f)
os.remove(file_path)
else:
raise Exception("Directory for storing iCals is not empty, "
"suggest running with -f to remove old files.")
else:
if os.path.exists(location):
if force:
os.remove(location)
else:
raise Exception("Output file already exists, suggest running "
"with -f to overwrite previous file.")
return location
def main():
args = parse_args()
yaml_dir = os.path.abspath(args.yaml_dir)
_check_if_location_exists(yaml_dir)
if args.ical_dir:
ical_dir = os.path.abspath(args.ical_dir)
_check_if_location_exists(ical_dir)
_check_if_location_exists(yaml_dir, style='d')
if os.listdir(ical_dir) != []:
if args.force:
for f in os.listdir(ical_dir):
file_path = os.path.join(ical_dir, f)
os.remove(file_path)
else:
raise Exception("Directory for storing iCals is not empty, "
"suggest running with -f to remove old files.")
ical.convert_yaml_to_ical(yaml_dir, outputdir=ical_dir)
meetings = meeting.load_meetings(yaml_dir)
# Check uniqueness and conflicts here before writing out to .ics
meeting.check_for_meeting_conflicts(meetings)
if args.ical_dir:
ical_dir = _prepare_output(args.ical_dir, style='d', force=args.force)
ical.convert_meetings_to_ical(meetings, outputdir=ical_dir)
else:
icalfile = os.path.abspath(args.icalfile)
if os.path.exists(icalfile):
if args.force:
os.remove(icalfile)
else:
raise Exception("Output file already exists, suggest running "
"with -f to overwrite previous file.")
ical.convert_yaml_to_ical(yaml_dir, outputfile=icalfile)
icalfile = _prepare_output(args.icalfile, force=args.force)
ical.convert_meetings_to_ical(meetings, outputfile=icalfile)
if args.index_template and args.index_output:
index_template = os.path.abspath(args.index_template)
_check_if_location_exists(index_template)
index_output = _prepare_output(args.index_output, force=args.force)
index.convert_meetings_to_index(
meetings, index_template, index_output)
if __name__ == '__main__':

View File

@ -17,8 +17,6 @@ import os
import os.path
import pytz
from yaml2ical import meeting
class Yaml2IcalCalendar(icalendar.Calendar):
"""A calendar in ics format."""
@ -78,22 +76,14 @@ class Yaml2IcalCalendar(icalendar.Calendar):
ics.write(self.to_ical())
def convert_yaml_to_ical(yaml_dir, outputdir=None, outputfile=None):
"""Convert meeting YAML files to iCal.
def convert_meetings_to_ical(meetings, outputdir=None, outputfile=None):
"""Converts a meeting list to iCal.
If meeting_list is specified, only those meetings in yaml_dir with
filenames contained in meeting_list are converted; otherwise,
all meeting in yaml_dir are converted.
:param yaml_dir: directory where meeting.yaml files are stored
:param meetings: list of meetings to convert
:param outputdir: location to store iCal files (one file per meeting)
:param outputfile: output iCal file (one single file for all meetings)
"""
meetings = meeting.load_meetings(yaml_dir)
# Check uniqueness and conflicts here before writing out to .ics
meeting.check_for_meeting_conflicts(meetings)
# convert meetings to a list of ical
if outputdir:

36
yaml2ical/index.py Normal file
View File

@ -0,0 +1,36 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import jinja2
import logging
import os
import os.path
def convert_meetings_to_index(meetings, template, output_file):
"""Creates index file from list of meetings.
:param meetings: list of meetings to convert
:param template: jinja2 template to use
:param output_file: output index file
"""
(template_dir, template_file) = os.path.split(template)
loader = jinja2.FileSystemLoader(template_dir)
env = jinja2.environment.Environment(trim_blocks=True, loader=loader)
template = env.get_template(template_file)
with open(output_file, "w") as out:
out.write(template.render(meetings=meetings))
logging.info('Wrote %d meetings to index.' % (len(meetings)))

View File

@ -29,6 +29,7 @@ class Schedule(object):
self.project = meeting.project
self.filefrom = meeting.filefrom
try:
self.utc = sched_yaml['time']
self.time = datetime.datetime.strptime(sched_yaml['time'], '%H%M')
self.day = sched_yaml['day']
self.irc = sched_yaml['irc']

View File

@ -40,6 +40,9 @@ class WeeklyRecurrence(object):
def rrule(self):
return {'freq': 'weekly'}
def __str__(self):
return "Weekly"
class BiWeeklyRecurrence(object):
"""Meetings occuring on alternate weeks.
@ -71,6 +74,9 @@ class BiWeeklyRecurrence(object):
def rrule(self):
return {'freq': 'weekly', 'interval': 2}
def __str__(self):
return "Every two weeks (on %s weeks)" % self.style
supported_recurrences = {
'weekly': WeeklyRecurrence(),