Skip to content

Instantly share code, notes, and snippets.

@cferwin
Created March 1, 2013 19:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cferwin/5066970 to your computer and use it in GitHub Desktop.
Save cferwin/5066970 to your computer and use it in GitHub Desktop.
A small script for maintaining my Zim-Wiki-based todo list.
# 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