Created
March 1, 2013 19:09
-
-
Save cferwin/5066970 to your computer and use it in GitHub Desktop.
A small script for maintaining my Zim-Wiki-based todo list.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Notebook Updater | |
# (c) 2013 Christopher Erwin | |
# | |
# This small application provides a CLI interface to a some generic functions | |
# to manipulate Zim wiki (http://www.zim-wiki.org) data files. | |
import os | |
import re | |
import argparse | |
from datetime import date | |
# ---------------------------------------- | |
# The CLI | |
# ---------------------------------------- | |
def main(): | |
# Set up arguments | |
# There is currently only one action. Since the action is destructive, I | |
# want it to be as intentional as possible. | |
parser = argparse.ArgumentParser() | |
parser.add_argument("action", help="What action to take", choices=["schedule"]) | |
parser.add_argument("-s", "--source", help="The file where the initial data is") | |
parser.add_argument("-d", "--destination", help="The file where the data is copied") | |
args = parser.parse_args() | |
source = os.environ['HOME'] + "/Notebooks/Notes/Home.txt" | |
destination = os.environ['HOME'] + "/Notebooks/Notes/Journal/" + str(date.today().isocalendar()[0]) + "/Week_" + str(get_week_number()) + ".txt" | |
if args.source: | |
source = args.source | |
if args.destination: | |
destination = args.destination | |
if args.action == "schedule": | |
schedule = get_section_from_file(source, "Schedule") | |
time_log = get_section_from_file(source, "Time Log") | |
day = get_section_from_file(source, "Day") | |
tomorrow = get_section_from_file(source, "Tomorrow") | |
destination_header = get_day_of_week() + " " + str(get_day_number()) + " " + get_month() | |
# Append Tomorrow to Day. The changes will be written to the source | |
# file, but not copied to the destination. | |
tomorrow = split_section(tomorrow) | |
day = day + "\n" + tomorrow[1] | |
# Copy over the Schedule and Time Log sections | |
write_section_to_file(destination, | |
set_header_level(destination_header, 1) + "\n\n" + schedule | |
+ "\n\n" + time_log, destination_header) | |
# Reset the Schedule section | |
schedule = reset_tasks(schedule) | |
write_section_to_file(source, schedule, "Schedule") | |
# Remove completed tasks from the Day section | |
day = remove_tasks(day, "*") | |
day = reset_tasks(day) | |
write_section_to_file(source, day, "Day") | |
# Clear the Time Log and Tomorrow sections | |
header_level = get_header_level(split_section(time_log)[0]) | |
write_section_to_file(source, set_header_level("Time Log", header_level), "Time Log") | |
header_level = get_header_level(tomorrow[0]) | |
write_section_to_file(source, set_header_level("Tomorrow", header_level), "Tomorrow") | |
# ---------------------------------------- | |
# Implementation functions | |
# ---------------------------------------- | |
# Get the line number where a section begins. The returned number is the line | |
# where the header is located. | |
def get_section_line_number(path, header, header_level=None): | |
line_number = 0 | |
# Read in the file text | |
f = open(path, "r") | |
lines = f.readlines() | |
f.close() | |
for line in lines: | |
line_number += 1 | |
# Find the header line | |
if line.strip(' \t\n=') == header: | |
# Determine if the line has the correct header level | |
if get_header_level(line) != None: | |
# If a header level was specified, see if it matches | |
if header_level != None: | |
if header_level == get_header_level(line): | |
header_level = get_header_level(line) | |
break | |
else: | |
# No header level was specified, but it is a valid header | |
header_level = get_header_level(line) | |
break | |
return line_number | |
# Get a section (header + text) from a file | |
def get_section_from_file(path, header, header_level=None): | |
f = open(path, "r") | |
text = f.readlines() | |
text = "".join(text) | |
section = get_section(text, header, header_level) | |
f.close() | |
return section | |
# Set a section (header + text) in a file | |
def write_section_to_file(path, new_section, header, header_level=None, max_blank_lines=1): | |
blank_line_count = 0 # For limiting the number of blank lines after | |
# before_section. TODO: There must be a better way to do this. | |
# Get the text from the file | |
f = open(path, "r") | |
file_text = f.readlines() | |
file_text = "".join(file_text) | |
f.close() | |
line_number = get_section_line_number(path, header, header_level) # Where to write the new section (location of the previous section) | |
old_section = get_section_from_file(path, header, header_level) # The section previously in the file | |
file_text = file_text.replace(old_section, "") # Remove the old file section from the file's text | |
# Get the text that comes before and after the section in the file. The | |
# entire file will be rewritten, not just the section, so this text makes | |
# up the rest of the file. | |
file_text = file_text.splitlines() | |
before_section = file_text[0:line_number] | |
after_section = file_text[line_number:] | |
open(path, "w").close() # Clear the file | |
f = open(path, "w") # Re-open the file to write the new section | |
# Write the file's text with the new section replacing the old section | |
for line in before_section: | |
# Keep track of blank lines in the file. | |
# FIXME: For some reason, two extra blank lines are written each time the function runs. | |
if line.strip() == "": | |
blank_line_count += 1 | |
else: | |
blank_line_count = 0 | |
if blank_line_count <= max_blank_lines: | |
f.write(line + "\n") | |
f.write(new_section) | |
f.write("\n") | |
for line in after_section: | |
f.write(line + "\n") | |
f.close() | |
# Get a full section (header + text including subsections) from a string of text | |
def get_section(text, header, header_level=None): | |
lines = text.splitlines() | |
found_header = False | |
section = "" | |
for line in lines: | |
# Find the header line | |
if line.strip(' \t\n=') == header: | |
# Determine if the line has the correct header level | |
if get_header_level(line) != None: | |
# If a header level was specified, see if it matches | |
if header_level != None: | |
if header_level == get_header_level(line): | |
header_level = get_header_level(line) | |
found_header = True | |
else: | |
# No header level was specified, but it is a valid header | |
header_level = get_header_level(line) | |
found_header = True | |
if found_header: | |
# Find the end of the section (the next section with the same header level) | |
line_header_level = get_header_level(line) | |
if line_header_level != None: | |
if line_header_level <= header_level and line.strip(' \t\n=') != header: | |
return section.strip(' \t\n') | |
# Get the section's text | |
section += line + "\n" | |
return section.strip(' \t\n') | |
# Get the numeric level of a header | |
def get_header_level(string): | |
splitter = re.compile(r'(\s+|\S+)') | |
string = splitter.findall(string) | |
prefix_count = None # Number of '=' in the first set | |
suffix_count = None # Number of '=' in the second set | |
count = None # Number of '=' in the set being counted | |
for s in string: | |
if '=' in s: | |
# The 7 corrects for the reversal in the number of '=' characters | |
# and the header level in Zim's markup. | |
count = 7 - len(s) | |
if prefix_count == None: | |
# Change to counting the second set of '=' when the first set | |
# has been counted. | |
prefix_count = count | |
else: | |
suffix_count = count | |
if prefix_count == suffix_count: | |
return prefix_count | |
else: | |
return None | |
# Format a string as a header of a specific level | |
def set_header_level(text, header_level): | |
header_level = 7 - header_level | |
formatting = ("=" * header_level) | |
return formatting + " " + text + " " + formatting | |
# Get the ISO week number | |
def get_week_number(date_source=date): | |
week = date_source.today().isocalendar()[1] | |
if week < 10: | |
week = "0" + str(week) | |
return str(week) | |
# Get the day of the month | |
def get_day_number(date_source=date): | |
return date_source.today().day | |
# Get the day of the week (Monday, Tuesday, etc) | |
def get_day_of_week(date_source=date): | |
number = date_source.today().weekday() | |
day = None | |
if number == 0: | |
day = "Monday" | |
elif number == 1: | |
day = "Tuesday" | |
elif number == 2: | |
day = "Wednesday" | |
elif number == 3: | |
day = "Thursday" | |
elif number == 4: | |
day = "Friday" | |
elif number == 5: | |
day = "Saturday" | |
elif number == 6: | |
day = "Sunday" | |
return day | |
# Get the month (January, February) | |
def get_month(date_source=date): | |
number = date_source.today().month | |
month = None | |
if number == 1: | |
month = "January" | |
elif number == 2: | |
month = "February" | |
elif number == 3: | |
month = "March" | |
elif number == 4: | |
month = "April" | |
elif number == 5: | |
month = "May" | |
elif number == 6: | |
month = "June" | |
elif number == 7: | |
month = "July" | |
elif number == 8: | |
month = "August" | |
elif number == 9: | |
month = "September" | |
elif number == 10: | |
month = "October" | |
elif number == 11: | |
month = "November" | |
elif number == 12: | |
month = "December" | |
return month | |
# Is the string a valid task? | |
def is_task(string, token=None): | |
string = string.strip() | |
if token: | |
valid = string.startswith("[" + token + "]") | |
else: | |
if len(string) > 3: | |
if string[0] == "[" and string[2] == "]": | |
valid = True | |
else: | |
valid = False | |
else: | |
valid = False | |
return valid | |
# Remove all tasks in the text which have the specified token | |
def remove_tasks(section, token=None): | |
lines = section.splitlines() | |
new_section = "" | |
for line in lines: | |
if not is_task(line, token): | |
new_section += line + "\n" | |
return new_section.strip() | |
# Resets all tasks to a blank state ("[ ] Foobar") | |
def reset_tasks(section): | |
lines = section.splitlines() | |
new_section = "" | |
for line in lines: | |
if is_task(line): | |
# Start of the task | |
start = line.find("[") | |
start = line[:start] | |
# End of the task | |
end = line.find("]") + 1 | |
# Text of the task | |
text = line[end:] | |
new_section += start + "[ ]" + text + "\n" | |
else: | |
new_section += line + "\n" | |
return new_section.strip() | |
# Separate the header and text of a section | |
def split_section(section): | |
lines = section.splitlines() | |
found_header = False | |
header = "" | |
text = "" | |
for line in lines: | |
if found_header == False and get_header_level(line) != None: | |
# Found the header | |
found_header = True | |
header = line | |
else: | |
text += line + "\n" | |
return (header.strip(), text.strip()) | |
# ---------------------------------------- | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment