aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Moch2018-02-24 19:07:03 -0500
committerDaniel Moch2018-02-24 19:07:03 -0500
commit3b8afdabcab2f058d1f10892b7b510729d2301cd (patch)
tree0eece8db35f407f83d6f2a0d44fd5ed10a8a4abd
parentb937c3dca9aeec657b9ea0b7d9c8709507acfcf0 (diff)
downloadcbr-schedule-3b8afdabcab2f058d1f10892b7b510729d2301cd.tar.gz
Add generate_schedule and support files
This top-level executable take the json input files generated by generate_readings and builds a reading schedule. The details of the plan have been pretty well parameterized into a config file (schedule.ini). There is still some refactoring to do, but I'm committing now since the utility works. UNHANDLED EDGE CASE: Leap years
-rwxr-xr-xgenerate_schedule53
-rw-r--r--readingplan.py198
-rw-r--r--schedule.ini30
3 files changed, 281 insertions, 0 deletions
diff --git a/generate_schedule b/generate_schedule
new file mode 100755
index 0000000..5f91eca
--- /dev/null
+++ b/generate_schedule
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+#
+# The MIT License (MIT)
+#
+# Copyright (c) 2018 Daniel Moch
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+from readingplan import ReadingPlan
+from sys import argv
+import configparser
+
+def print_usage():
+ print('Usage: generate_schedule [config]')
+ print('\tconfig - Path to schedule config file (default: schedule.ini)')
+
+def generate_schedule(configfile='schedule.ini'):
+ """
+ Generate the reading plan, using the ReadingPlan class as a
+ generator. This function takes the day-of-week that January 1st
+ falls on as its single parameter.
+ """
+ config = configparser.ConfigParser()
+ config.read(configfile)
+ plan = ReadingPlan()
+ plan.build_plan(config)
+ while not plan.done:
+ print(plan.next_reading())
+
+if __name__ == "__main__":
+ if len(argv) > 2:
+ print_usage()
+ exit(1)
+ elif len(argv) < 2:
+ generate_schedule()
+ else:
+ generate_schedule(argv[1])
diff --git a/readingplan.py b/readingplan.py
new file mode 100644
index 0000000..1f11d9f
--- /dev/null
+++ b/readingplan.py
@@ -0,0 +1,198 @@
+#
+# The MIT License (MIT)
+#
+# Copyright (c) 2018 Daniel Moch
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+import json
+
+DAYS_OF_WEEK = (
+ "Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ )
+
+MONTHS = (
+ ('January', 31,),
+ ('February', 28,),
+ ('March', 31,),
+ ('April', 30,),
+ ('May', 31,),
+ ('June', 30,),
+ ('July', 31,),
+ ('August', 31,),
+ ('September', 30,),
+ ('October', 31,),
+ ('November', 30,),
+ ('December', 31,),
+ )
+
+class ReadingPlan:
+ """
+ Once initialized, maintain state of the plan so the schedule
+ function can simply ask for the next day's reading.
+ """
+ _initialized = False
+ _current_month = 0
+ _current_day = 1
+ _day_of_week = 0
+
+ _sections = {}
+ _days_sections = {}
+ _days_sections_fallback = {}
+ _reading_dictionaries = {}
+ _reading_indices = {}
+ done = False
+
+ def _general_init(self, config):
+ jan1_day_of_week = config['general']['jan1_day_of_week']
+ for idx, day in enumerate(DAYS_OF_WEEK):
+ if day.upper() == jan1_day_of_week.upper():
+ print('Setting _day_of_week to ' + str(idx))
+ self._day_of_week = idx
+ break
+
+ def _days_sections_init(self, config, section):
+ for day in DAYS_OF_WEEK:
+ day_key = day.lower()
+ if day_key in config[section].keys():
+ self._days_sections[day] = config[section][day_key]
+ else:
+ print('No readings planned for ' + day + 's')
+ self._days_sections[day] = None
+
+ def _days_sections_fallback_init(self, config, section):
+ for day in DAYS_OF_WEEK:
+ day_key = day.lower()
+ if day_key in config[section].keys():
+ self._days_sections_fallback[day] = \
+ config[section][day_key]
+ else:
+ print('No readings planned for ' + day + 's')
+ self._days_sections_fallback[day] = None
+
+ def _section_init(self, config, section):
+ if 'name' in config[section].keys():
+ self._sections[section] = config[section]['name']
+ else:
+ print('No section name specified for ' + section)
+ print('Using ' + section + ' as name')
+ self._sections[section] = section
+ reading_dictionary = []
+ with open('sections/' + section + '.json', 'r') as sfile:
+ reading_dictionary = json.load(sfile)
+ self._reading_dictionaries[section] = reading_dictionary
+ if 'book' in config[section].keys():
+ book = config[section]['book']
+ if 'chapter' in config[section].keys():
+ chapter = config[section]['chapter']
+ else:
+ chapter = '1'
+ for dictionary in reading_dictionary:
+ if dictionary['book'] == book and \
+ dictionary['reading'] == chapter:
+ self._reading_indices[section] = \
+ dictionary['index']
+ if section not in self._reading_indices.keys():
+ self._reading_indices[section] = 0
+
+ def _print_plan_info(self):
+ print('_sections:' + str(self._sections.keys()))
+ print('_reading_dictionaries: ' + \
+ str(self._reading_dictionaries.keys()))
+ print('_reading_indices: ' + str(self._reading_indices.values()))
+ print('_day_of_week: ' + str(self._day_of_week))
+ print('_days_sections: ' + str(self._days_sections.keys()))
+ print('_days_sections_fallback: ' +
+ str(self._days_sections_fallback.values()))
+
+ def build_plan(self, config):
+ for section in config.sections():
+ if section == 'general':
+ self._general_init(config)
+ elif section == 'days_sections':
+ self._days_sections_init(config, section)
+ elif section == 'days_sections_fallback':
+ self._days_sections_fallback_init(config, section)
+ else:
+ self._section_init(config, section)
+ self._print_plan_info()
+ self._initialized = True
+
+ def _next_day(self):
+ if self._current_day == MONTHS[self._current_month][1]:
+ self._current_day = 1
+ self._current_month += 1
+ if self._current_month == len(MONTHS):
+ self.done = True
+ else:
+ self._current_day += 1
+
+ last_day = len(DAYS_OF_WEEK) - 1
+ if self._day_of_week == last_day:
+ self._day_of_week = 0
+ else:
+ self._day_of_week += 1
+
+ def _readings(self, sections):
+ readings = []
+ if sections is not None:
+ for section in sections.split(','):
+ if section not in self._sections.keys():
+ raise ValueError(section + ' is not a known section')
+
+ section_dict = self._reading_dictionaries[section]
+ for reading_dict in section_dict:
+ if reading_dict['index'] == self._reading_indices[section]:
+ reading = {}
+ reading['section'] = self._sections[section]
+ reading['book'] = reading_dict['book']
+ reading['reading'] = reading_dict['reading']
+ readings.append(reading)
+ self._reading_indices[section] += 1
+ break
+ return readings
+
+ def next_reading(self):
+ if not self._initialized:
+ raise RuntimeError('Attempting to use an uninitialized ReadingPlan')
+ results = {}
+ readings = []
+ today_sections = self._days_sections[DAYS_OF_WEEK[self._day_of_week]]
+ today_sections_fallback = \
+ self._days_sections_fallback[DAYS_OF_WEEK[self._day_of_week]]
+ for reading in self._readings(today_sections):
+ readings.append(reading)
+
+ if readings == []:
+ for reading in self._readings(today_sections_fallback):
+ readings.append(reading)
+ results = {
+ 'day_of_week': self._day_of_week,
+ 'day': self._current_day,
+ 'month': MONTHS[self._current_month][0],
+ 'readings': readings
+ }
+ self._next_day()
+ return results
diff --git a/schedule.ini b/schedule.ini
new file mode 100644
index 0000000..8a1952e
--- /dev/null
+++ b/schedule.ini
@@ -0,0 +1,30 @@
+[general]
+jan1_day_of_week=Monday
+
+[new_testament]
+name=New Testament
+
+[old_testament]
+name=Old Testament
+book=Ecclesiastes
+chapter=12
+
+[psalms]
+name=Psalms
+book=Psalm
+chapter=105
+
+[days_sections]
+monday=new_testament,old_testament
+tuesday=new_testament,old_testament
+wednesday=new_testament,old_testament
+thursday=new_testament,old_testament
+friday=new_testament,old_testament
+saturday=psalms
+
+[days_sections_fallback]
+monday=psalms
+tuesday=psalms
+wednesday=psalms
+thursday=psalms
+friday=psalms