示例#1
0
def sent(handler):
	handler.title('Messages')
	requirePriv(handler, 'User')

	print tabs.where('sent')
	undelay(handler)

	Markdown.head('div.message .body pre code')

	messages = Message.loadAll(senderid = handler.session['user'].id, orderby = '-timestamp')
	for message in messages:
		printMessage(message, False)
示例#2
0
def showSprintRetrospective(handler, id):
	requirePriv(handler, 'User')
	id = int(id)
	sprint = Sprint.load(id)
	if not sprint or sprint.isHidden(handler.session['user']):
		ErrorBox.die('Sprints', "No sprint with ID <b>%d</b>" % id)
	elif not sprint.canView(handler.session['user']):
		ErrorBox.die('Private', "You must be a sprint member to view this sprint")
	editing = (sprint.owner == handler.session['user'])

	handler.title(sprint.safe.name)
	drawNavArrows(sprint, handler.session['user'], 'retrospective')
	print tabs(sprint, ('wrapup', 'retrospective'))

	Markdown.head()
	print "<script type=\"text/javascript\">"
	print "var sprint_id = %d;" % sprint.id
	print "var editing = %s;" % toJS(editing)
	print "</script>"

	if not (sprint.isReview() or sprint.isOver()):
		ErrorBox.die('Sprint Open', "The retrospective isn't available until the sprint is in review")

	retro = Retrospective.load(sprint)
	if retro is None:
		if editing:
			print "<form method=\"post\" action=\"/sprints/%d/retrospective/start\">" % sprint.id
			print Button("Start Retrospective").post().positive()
			print "</form>"
		else:
			print ErrorBox("This sprint has no retrospective")
		done()

	print "The sprint retrospective asks two main questions:<br>"
	print "<ul class=\"retrospective-list\">"
	print "<li class=\"good\">What went well during the sprint?</li>"
	print "<li class=\"bad\">What could be improved in the next sprint?</li>"
	print "</ul>"
	if editing:
		print "For now the list of categories is fixed; eventually you'll be able to modify them per-sprint. Click an existing item to edit it, or the margin icon to toggle it. Fill in the text area at the end of the category to add a new item; clear a text area to delete it. <a href=\"/help/markdown\">Markdown</a> is available if necessary"

	print "<div class=\"retrospective markdown\">"
	for category, entries in retro.iteritems():
		print "<div class=\"category\" data-id=\"%d\">" % category.id
		print "<div class=\"name\">%s</div>" % stripTags(category.name)
		for entry in sorted(entries, key = lambda entry: 0 if entry.good else 1):
			print "<div class=\"entry %s\" data-id=\"%d\"><textarea>%s</textarea></div>" % ('good' if entry.good else 'bad', entry.id, stripTags(entry.body))
		if not editing and len(entries) == 0:
			print "<div class=\"none\">No entries</div>"
		print "</div>"
	print "</div>"
示例#3
0
def inbox(handler):
	handler.title('Messages')
	requirePriv(handler, 'User')

	print InfoBox("Note", "You can control your automated message subscriptions from the <a href=\"/prefs\">preferences</a> page")

	print tabs.where('inbox')
	undelay(handler)

	Markdown.head('div.message .body pre code')

	messages = Message.loadAll(userid = handler.session['user'].id, orderby = '-timestamp')
	for message in messages:
		printMessage(message, True)
		if not message.read:
			message.read = True
			message.save()
示例#4
0
	def render(self):
		for case in switch(self.language):
			if case('markdown'):
				return Markdown.render(self.body)
			elif case('html'):
				return self.body
			else: # Shouldn't happen
				return self.body
示例#5
0
def sprintRetrospectiveRender(handler, sprintid, p_id, p_catid, p_body = None, p_good = None):
	def die(msg):
		print msg
		done()

	handler.wrappers = False
	if p_id != 'new':
		p_id = to_int(p_id, 'id', die)
	p_catid = to_int(p_catid, 'catid', die)
	if p_good is not None:
		p_good = to_bool(p_good)
	if not handler.session['user']:
		die("You must be logged in to modify sprint info")

	sprintid = int(sprintid)
	sprint = Sprint.load(sprintid)
	if not sprint or sprint.isHidden(handler.session['user']):
		die("There is no sprint with ID %d" % sprintid)
	elif sprint.owner != handler.session['user'] and (p_body is not None or p_good is not None):
		die("Only the scrummaster can edit the retrospective")
	elif not (sprint.isReview() or sprint.isOver()):
		die("The retrospective isn't available until the sprint is in review")

	if p_id == 'new':
		if p_body is None or p_good is None:
			die("Missing body/good")
		entry = RetroEntry(p_catid, p_body, p_good)
	else:
		entry = RetroEntry.load(p_id)
		if p_body is not None:
			entry.body = p_body
		if p_good is not None:
			entry.good = p_good

		if entry.body == '':
			entry.delete()
			handler.responseCode = 299
			print toJS({'id': entry.id, 'deleted': True});
			return

	entry.save()
	handler.responseCode = 299
	print toJS({'id': entry.id, 'body': Markdown.render(entry.body), 'good': entry.good})
示例#6
0
文件: users.py 项目: mrozekma/Sprint
def user(handler, username):
	user = User.load(username = username)
	if not user:
		ErrorBox.die('User', "No user named <b>%s</b>" % stripTags(username))

	Markdown.head('form#message-form .body pre code')
	print "<script src=\"/static/jquery.typing-0.2.0.min.js\" type=\"text/javascript\"></script>"
	print "<script src=\"/static/users.js\" type=\"text/javascript\"></script>"
	Chart.include()
	undelay(handler)

	handler.title(user.safe.username)
	handler.replace('$bodytitle$', '', 1)
	print "<img src=\"%s\" class=\"gravatar\">" % user.getAvatar(64)
	print "<h1>%s</h1>" % user.safe.username
	if isDevMode(handler):
		print "<div class=\"debugtext\">User ID: %d</div>" % user.id
	print "<div class=\"clear\"></div>"

	if handler.session['user'] and handler.session['user'].hasPrivilege('Admin'):
		print "<h3>Admin</h3>"
		print "<form method=\"post\" action=\"/admin/users\">"
		print "<input type=\"hidden\" name=\"username\" value=\"%s\">" % user.username
		print "<button type=\"submit\" class=\"btn\" name=\"action\" value=\"resetpw\">Reset password</button>"
		print "<button type=\"submit\" class=\"btn\" name=\"action\" value=\"impersonate\">Impersonate</button>"
		print "<button type=\"submit\" class=\"btn\" name=\"action\" value=\"sessions\">Manage sessions</button>"
		print "<button type=\"submit\" class=\"btn\" name=\"action\" value=\"privileges\">Manage privileges</button>"
		print "</form>"

	if user == handler.session['user']:
		print "<h3>Avatar</h3>"
		if user.hasLocalAvatar():
			print "Your avatar is currently <a href=\"/users/%s/avatar/set\">locally hosted</a>" % user.username
		else:
			print "Your avatar can be changed at <a href=\"http://gravatar.com/\" target=\"_new\">http://gravatar.com/</a>. It must be associated with the e-mail <b>%s</b>, and be rated PG. You can also host an avatar <a href=\"/users/%s/avatar/set\">locally</a>, if necessary" % (user.getEmail(), user.username)

		print "<h3>Authentication</h3>"
		print "Your sprint tool password can be changed <a href=\"/resetpw\">here</a>.",
		if settings.kerberosRealm:
			print "You can also use your %s kerberos password to login" % settings.kerberosRealm,
		print "<br><br>"
		if user.hotpKey == '':
			print "You also have the option to use two-factor authentication via <a href=\"http://en.wikipedia.org/wiki/HOTP\">HOTP</a>. You can use <a href=\"http://support.google.com/a/bin/answer.py?hl=en&answer=1037451\">Google Authenticator</a> to generate verification codes<br><br>"
			print "<form method=\"post\" action=\"/security/two-factor\">"
			print "<button type=\"submit\" class=\"btn danger\" name=\"action\" value=\"enable\">Enable two-factor authentication</button>"
			print "</form>"
		else:
			print "You are currently using two-factor authentication<br><br>"
			print "<form method=\"post\" action=\"/security/two-factor\">"
			print "<button type=\"submit\" class=\"btn danger\" name=\"action\" value=\"enable\">Reset HOTP key</button>"
			print "<button type=\"submit\" class=\"btn danger\" name=\"action\" value=\"disable\">Disable two-factor authentication</button>"
			print "</form>"

		print "<h3>Messages</h3>"
		print "Your inbox and sent messages can be viewed <a href=\"/messages/inbox\">here</a><br>"

	print "<h3>Last seen</h3>"
	if not user.lastseen:
		print "Never"
	elif dateToTs(getNow()) - user.lastseen < 60:
		print "Just now"
	else:
		print "%s ago" % timesince(tsToDate(user.lastseen))

	if handler.session['user'] and handler.session['user'] != user:
		print "<h3>Message</h3>"
		print "<small>(Messages are formatted in <a target=\"_blank\" href=\"/help/markdown\">markdown</a>)</small>"
		print "<form id=\"message-form\" method=\"post\" action=\"/messages/send\">"
		print "<input type=\"hidden\" name=\"userid\" value=\"%d\">" % user.id
		print "<textarea name=\"body\" class=\"large\"></textarea>"
		print "<div class=\"body markdown\"><div id=\"preview\"></div></div>"
		print Button('Send').post().positive()
		print "</form>"

	print "<h3>Project distribution</h3>"
	sprints = filter(lambda s: user in s.members, Sprint.loadAllActive())
	sprintHours = map(lambda s: (s, Availability(s).getAllForward(getNow(), user)), sprints)
	projectHours = map(lambda (p, g): (p, sum(hours for sprint, hours in g)), groupby(sprintHours, lambda (s, a): s.project))

	# For now at least, don't show projects with no hours
	projectHours = filter(lambda (p, h): h > 0, projectHours)
	if len(projectHours) > 0:
		chart = Chart('chart')
		chart.title.text = ''
		chart.tooltip.formatter = "function() {return '<b>' + this.point.name + '</b>: ' + this.point.y + '%';}"
		chart.plotOptions.pie.allowPointSelect = True
		chart.plotOptions.pie.cursor = 'pointer'
		chart.plotOptions.pie.dataLabels.enabled = False
		chart.plotOptions.pie.showInLegend = True
		chart.credits.enabled = False
		chart.series = seriesList = []

		series = {
			'type': 'pie',
			'name': '',
			'data': []
		}
		seriesList.append(series)

		total = sum(hours for project, hours in projectHours)
		for project, hours in projectHours:
			series['data'].append([project.name, float("%2.2f" % (100 * hours / total))])

		chart.js()
		chart.placeholder()
	else:
		print "Not a member of any active sprints"
示例#7
0
    # Make sure file exists
    if (not isfile(args.input_file)):
        print(f"{c.FAIL}Error:{c.ENDC} Input file does not exist.")
        sys.exit(1)

    # Record start time
    t1 = datetime.now()

    # Otherwise, process the input file
    print(f"Processing {c.UNDERLINE}{args.input_file}{c.ENDC} ... ")

    # Set document variables
    word_count, sentence_count, paragraph_count, overused_phrase_count, repeated_word_count, avoid_word_count, complex_words, syllable_count, fog_index, reading_ease, grade_level = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0

    # Instantiate Markdown parser
    md = Markdown()

    # Read the template
    fd = open("./assets/template.html", "r")
    template = fd.read().split("<!-- DIVIDER -->")
    fd.close()

    # Open the output file
    if (args.output_file == None):
        args.output_file = "./index.html"
    open(args.output_file, "w").close()
    outfile = open(args.output_file, "a")

    # Process file
    infile = open(args.input_file, "r")
    
示例#8
0
文件: Note.py 项目: mrozekma/Sprint
	def render(self):
		return Markdown.render(self.body)
示例#9
0
文件: tasks.py 项目: nolandda/Sprint
def task(handler, ids):
    requirePriv(handler, "User")
    Chart.include()

    Markdown.head(".note .text .body pre code")
    print '<script src="/static/jquery.typing-0.2.0.min.js" type="text/javascript"></script>'
    undelay(handler)

    tasks = {}
    if "," not in ids:  # Single ID
        ids = [int(ids)]
        tasks[ids[0]] = Task.load(ids[0])

        def header(task, text, level):
            if level == 1:
                handler.title(text)
            else:
                print "<h%d>%s</h%d>" % (level, text, level)

    else:  # Many IDs
        ids = map(int, uniq(ids.split(",")))
        tasks = {id: Task.load(id) for id in ids}
        handler.title("Task Information")

        if not all(tasks.values()):
            ids = [str(id) for (id, task) in tasks.iteritems() if not task]
            ErrorBox.die(
                "No %s with %s %s"
                % ("task" if len(ids) == 1 else "tasks", "ID" if len(ids) == 1 else "IDs", ", ".join(ids))
            )

        if len(set(task.sprint for task in tasks.values())) == 1:  # All in the same sprint
            print '<small>(<a href="/sprints/%d?search=highlight:%s">Show in backlog view</a>)</small><br><br>' % (
                tasks.values()[0].sprint.id,
                ",".join(map(str, ids)),
            )

        for id in ids:
            print '<a href="#task%d">%s</a><br>' % (id, tasks[id].safe.name)

        def header(task, text, level):
            if level == 1:
                print "<hr>"
                print '<a name="task%d"></a>' % task.id
                print '<a href="#task%d"><h2>%s</h2></a>' % (task.id, text)
            else:
                print "<h%d>%s</h%d>" % (level + 1, text, level + 1)

    for id in ids:
        task = tasks[id]
        if not task or task.sprint.isHidden(handler.session["user"]):
            ErrorBox.die("Tasks", "No task with ID <b>%d</b>" % id)
        elif not task.sprint.canView(handler.session["user"]):
            ErrorBox.die("Private", "You must be a sprint member to view this sprint's tasks")
        revs = task.getRevisions()
        startRev = task.getStartRevision()

        header(task, task.safe.name, 1)

        header(task, "Info", 2)
        print 'Part of <a href="/sprints/%d">%s</a>, <a href="/sprints/%d#group%d">%s</a>' % (
            task.sprintid,
            task.sprint,
            task.sprintid,
            task.groupid,
            task.group,
        ),
        if task.goal:
            print 'to meet the goal&nbsp;&nbsp;<img class="bumpdown" src="/static/images/tag-%s.png">&nbsp;<a href="/sprints/%d?search=goal:%s">%s</a>' % (
                task.goal.color,
                task.sprintid,
                task.goal.color,
                task.goal.safe.name,
            ),
        print "<br>"
        print "Assigned to %s<br>" % ", ".join(map(str, task.assigned))
        print "Last changed %s ago<br><br>" % timesince(tsToDate(task.timestamp))
        hours, total, lbl = task.hours, startRev.hours, "<b>%s</b>" % statuses[task.status].text
        if task.deleted:
            if task.sprint.canEdit(handler.session["user"]):
                print '<form method="post" action="/sprints/%d">' % task.sprint.id
                print '<input type="hidden" name="id" value="%d">' % task.id
                print '<input type="hidden" name="rev_id" value="%d">' % task.revision
                print '<input type="hidden" name="field" value="deleted">'
                print '<input type="hidden" name="value" value="false">'
                print "Deleted (%s)" % Button("undelete", id="undelete").mini().positive()
                print "</form>"
            else:
                print "Deleted"
            print "<br>"
        elif task.status == "complete":
            print ProgressBar(lbl, total - hours, total, zeroDivZero=True, style="progress-current-green")
        elif task.status in ("blocked", "canceled", "deferred", "split"):
            hours = filter(lambda rev: rev.hours > 0, revs)
            hours = hours[-1].hours if len(hours) > 0 else 0
            print ProgressBar(lbl, total - hours, total, zeroDivZero=True, style="progress-current-red")
        else:
            print ProgressBar(lbl, total - hours, total, zeroDivZero=True)

        header(task, "Notes", 2)
        for note in task.getNotes():
            print '<div id="note%d" class="note">' % note.id
            print '<form method="post" action="/tasks/%d/notes/%d/modify">' % (id, note.id)
            print '<div class="avatar"><img src="%s"></div>' % note.user.getAvatar()
            print '<div class="text">'
            print '<div class="title"><a class="timestamp" href="#note%d">%s</a> by <span class="author">%s</span>' % (
                note.id,
                tsToDate(note.timestamp).replace(microsecond=0),
                note.user.safe.username,
            )
            if note.user == handler.session["user"]:
                print '<button name="action" value="delete" class="fancy mini danger">delete</button>'
            print "</div>"
            print '<div class="body markdown">%s</div>' % note.render()
            print "</div>"
            print "</form>"
            print "</div>"

        print '<div class="note new-note">'
        print '<form method="post" action="/tasks/%d/notes/new">' % id
        print '<div class="avatar"><div><img src="%s"></div></div>' % handler.session["user"].getAvatar()
        print '<div class="text">'
        print '<div class="title">'
        print "<b>New note</b>"
        print '<a target="_blank" href="/help/markdown" class="fancy mini">help</a>'
        print "</div>"
        print '<div class="body"><textarea name="body" class="large"></textarea></div>'
        print Button("Post").post().positive()
        print "<hr>"
        print '<div class="body markdown"><div id="preview"></div></div>'
        print "</div>"
        print "</form>"
        print "</div>"

        print '<button class="btn start-new-note">Add Note</button>'
        print '<div class="clear"></div>'

        header(task, "History", 2)
        chart = TaskChart("chart%d" % id, task)
        chart.js()

        chart.placeholder()
        showHistory(task, False)
        print "<br>"
示例#10
0
def GenFile(iname):
    # Instantiate document statistics
    #   fk_wc is a special word count for the Flesch-Kincaid readability test
    #   word_count is a by-paragraph word count
    #   total_sentences is a count of all sentences in document
    #   total_word_count is the word count for the entire document
    #   total_overused_words is the count of overused words
    #   total_repeated_words is the count of unique repeated words in the document,
    #       not including the individual repeats
    #   total_avoid_words is the ocunt of words to avoid in the document
    #   complex_words is a running count of words with over three syllables
    #   syllable_count is a running cound of syllables in the document
    fk_wc = 0
    word_count = []
    total_sentences = 0
    total_word_count = 0
    total_overused_words = 0
    total_repeated_words = 0
    total_avoid_words = 0
    complex_words = 0
    syllable_count = 0

    # Open th template file, read its contents, and split them for easy access later
    template_fd = open("template.html", "r")
    template = template_fd.read().split("<!--Divider-->")
    template_fd.close()

    # Clear the output file, then write the opening HTML tags
    o_fd = open("index.html", "w").close()
    o_fd = open("index.html", "a")
    o_fd.write(template[0])

    # Open the source file
    fd = open(iname, "r")

    # Read the title from the source file
    title = fd.readline().strip()
    if (title[0] == "#"):
        title = title.split("](")
        title = "<h2 class='linkpost'><a href=\"" + title[
            1][:-3] + "\">" + title[0][3:] + "</a></h2>"
    else:
        title = "<h2 class='original'>" + title + "</h2>\n"

    # Get rid of the title separator (=) and the following blank line
    fd.readline()

    # Write the opening <article> tag and article title
    o_fd.write("<article>\n")
    o_fd.write(title)

    block = False
    # Iterate over each line in the file
    for line in iter(fd.readline, ""):
        fk_wc += line.count(" ") + 1

        # Save a "backup" of the line, for searching a sanitized version of it
        backup = line

        # If we're looking at an empty line, just skip over it; else continue
        if (len(line.strip()) == 0):
            continue

        # Do not collect stats on code snippets. Write them to the file and
        # move on.
        if (line[0:4] == "<pre" or block == True):
            if (line.find("</pre>") == -1):
                block = True
            else:
                block = False

        # Do not collect stats on images. Write them to the file and move on.
        if (line[0:2] == "![" or line[0:4] == "<pre"):
            o_fd.write(Markdown(line, "https://zacs.site/") + "\n")
            continue

        # Instantiate paragraph-specific statistics
        wc = 0  # Word count for current paragraph
        overused_words = 0  # Number of overused words
        repeated_words = 0  # Number of repeated words
        avoid_words = 0  # Number of words to avoid
        dict_count = {}  # A dictionary that will count occurences of each word

        # For each word in the list of overused words to avoid, search the
        # paragraph case insensitively. If a match is found, increment the count
        # of overused words in the paragraph and document, then highlight it.
        for word in overlap:
            m = re.search("[^\w]" + word + "[^\w]", line, re.IGNORECASE)
            if (m):
                overused_words += line.lower().count(word)
                total_overused_words += overused_words

                # The first replace will capture matches with uppercase letters
                # that start a sentence, or regular lowercase words; if the first
                # replace targeted matches with uppercase letters that start a
                # sentence, the second replace will capture all other occurences of
                # that word that may exist throughout the document.
                line = line.replace(
                    m.group(0),
                    " <span class='replace'>" + m.group(0) + "</span> ")
                if (m.group(0) != m.group(0).lower()):
                    line = line.replace(
                        m.group(0).lower(), " <span class='replace'>" +
                        m.group(0).lower().strip() + "</span> ")

        # For each word in the sentence, count repetitions. If there are three or more
        # of the same word in a sentnece, highlight all occurences. Also check for be
        # verbs as well, and highlight them accordingly.
        # for word in backup.split(" "):
        for word in re.split("(\s|--)", backup):

            if ("](" in word):
                word = word.split("](")[0]

            # This strips any special characters from the word, such as punctuation.
            stripped = re.sub(r"^[\W]+", "", word.strip())
            stripped = re.sub(r"[\W]+$", "", stripped)

            if (len(stripped) == 0):
                continue

            wc += 1

            # First check if we have decided to exclude the word, as in the case of "the",
            # "of", "a", "for", or similar words. If true, skip the word; else, proceed.
            if (stripped.lower() not in exclude):
                # If the word already exists in the dictionary, increment its count; else
                # instantiate it to 1
                if (stripped.lower() in dict_count):
                    dict_count[stripped.lower()] += 1
                else:
                    dict_count[stripped.lower()] = 1

                # Once there are at least three occurences of a word in the paragraph,
                # highlight it as a repeat word and incrememnt the number of unique words
                # repeated in the document.
                if (dict_count[stripped.lower()] == 3):
                    line = re.sub(
                        r"([^\w])" + stripped + r"([^\w])",
                        r"\1<span class='repeat " + stripped + "'>" +
                        stripped + r"</span>\2", line)
                    repeated_words += 1
                    total_repeated_words += 1

            # Check for be verbs, "ly" words in the document. If found, highlight
            # them and increment the be verb count.
            if (stripped.lower() in be_verbs) or (stripped.lower()[-2:]
                                                  == "ly"):
                line = re.sub(
                    r"([^\w])" + stripped + r"([^\w])",
                    r"\1<span class='avoid'>" + stripped + r"</span>\2", line)
                avoid_words += 1
                total_avoid_words += 1

            # To calculate the number of complex words, first exclude proper nouns. Next,
            # exclude compound words, then strip -es, -ed, and -ing endings. Finally, if
            # the number of syllables in the remaining word is >= 3, found a complex word.
            if (not (re.search("^[A-Z]", stripped))):
                if ("-" not in stripped):
                    if (SyllableCount(stripped.lower()) >= 3):
                        start = line.find(word)
                        length = len(word)
                        end = start + length

                        # print("Searched: '%s'" % line[start:end])
                        # print("With ends: '%s'" % line[start-1:end+1])
                        # print("Preceeding character: '%s'" % line[start-1])
                        # print("After character: '%s'" % line[end])
                        # print(re.match("[\>\w]", line[start-1]))
                        # print(re.match("[\<\w]", line[end]))
                        # print(line)
                        # print
                        if not ("http" in stripped
                                or re.match("[\>\w]", line[start - 1])
                                or re.match("[\<\w]", line[end])):
                            line = line.replace(
                                stripped, "<span class='complex_word'>" +
                                stripped + "</span>")
                        # line = re.sub((r"[^\>\w]")+stripped+(r"[^\<\w]"), "\1<span class='complex_word'>"+stripped+"</span>\2", line)
                        complex_words += 1
                        # sleep(1)

            syllable_count += SyllableCount(stripped.lower())

        word_count.append(wc)

        # Count sentences in paragraph, and add that number to the running sentence total
        sentences = (len(re.findall("\.[^\w]", line)) +
                     len(re.findall("[?!]", line))) or 1
        total_sentences += sentences

        if (line[0:1] != "* " and line[0] != "#" and line[0:3] != "<pre"
                and block == False and line[-7:].strip() != "</pre>"):
            # Write the paragraph stats div to the output file, then the parsed line.
            o_fd.write(
                "<div class='floating_stats'><div>Words: %d. Sentences: %d</div><div>Overused phrase: %d</div><div>Repeated: %d; Avoid: %d</div></div>\n"
                % (word_count[-1], sentences, overused_words, repeated_words,
                   avoid_words))
        o_fd.write(Markdown(line, "https://zacs.site/") + "\n")

    # Close the source file
    fd.close()

    # Write closing <article> tag
    o_fd.write("</article>")

    # Sum the paragraph word counts into a single count, for document stats
    for num in word_count:
        total_word_count += int(num)

    # Get a timestamp for the document stats
    d = datetime.datetime.now()
    utime = "%d-%d-%d %d:%d:%d" % (d.year, d.month, d.day, d.hour, d.minute,
                                   d.second)

    # Calculate Gunning Fog Index
    # estimates the years of formal education needed to understand the text on a first reading.
    gfi = 0.4 * (float(total_word_count) / float(total_sentences) +
                 100.0 * float(complex_words) / float(total_word_count))

    # Calculate Flesch-Kincaid Readability Test
    # higher scores indicate material that is easier to read; lower numbers indicate difficulty.
    fkr = 206.835 - 1.015 * (float(fk_wc) / float(total_sentences)) - 84.6 * (
        float(syllable_count) / float(fk_wc))

    if (fkr <= 30.0):
        fkr = "<span class='extreme'>%3.2f</span>" % (fkr)
    elif (fkr <= 50.0):
        fkr = "<span class='hard'>%3.2f</span>" % (fkr)
    elif (fkr <= 60.0):
        fkr = "<span class='tough'>%3.2f</span>" % (fkr)
    elif (fkr <= 70.0):
        fkr = "<span class='plain'>%3.2f</span>" % (fkr)
    elif (fkr <= 80.0):
        fkr = "<span class='fair'>%3.2f</span>" % (fkr)
    elif (fkr <= 90.0):
        fkr = "<span class='easy'>%3.2f</span>" % (fkr)
    elif (fkr <= 100.00):
        fkr = "<span class='simple'>%3.2f</span>" % (fkr)

    # Calculate the Flesch-Kincaid Grade level:
    # the number of years of education generally required to understand this text.
    fgl = 0.39 * float(total_word_count) / float(
        total_sentences) + 11.8 * float(syllable_count) / float(
            total_word_count) - 15.59

    # Write the closing HTML to the output file, with document stats. Close it.
    o_fd.write(template[1] %
               (utime, utime, total_word_count, str(total_word_count / 200.0) +
                " mins", total_sentences, len(word_count),
                total_word_count / len(word_count), total_overused_words,
                total_repeated_words, total_avoid_words, gfi, fkr, fgl))
    o_fd.close()