PushPost

by Patrick on August 04, 2014 at 17:29

For a few years now I've been meaning to build a tool that would help create a simple website. Finally, it's more or less complete. Well, at least enough of it is complete to write this post in and generate all the pages in the "Code" section of this site.

I originally had something much simpler in mind; it was going to be a small script that would just insert a few new paragraphs into an existing (and handmade) html file. Once I actually sat down and started planning it out I came to the conclusion that many bugs could be avoided by generating everything in one program. In order to do this - and maintain multiple pages while adding new posts to the top of a page - I would need to use a database.

protected void CommitPost(PostTableLayer newPost, PostsDataContext database)
{
    // Check to see if it already exists in the database.
    if (database.Posts.Where(p => p.UniqueID == newPost.UniqueID).Count() > 0)
        return;
    // Insert newPost into Posts table
    database.Posts.InsertOnSubmit(newPost);
    // Insert newPost's footers into the Footnotes table
    foreach (Footer f in newPost.Footers)
    {
        f.PostID = newPost.UniqueID;
        f.ForceNewUniqueID();
        database.Footnotes.InsertOnSubmit(f);
    }
    // Insert newPost's tags into the Tags table
    foreach (Tag t in newPost.Tags)
    {
        t.PostID = newPost.UniqueID;
        t.ForceNewUniqueID();
        database.Tags.InsertOnSubmit(t);
    }
}

Once I'd decided to use a database, I could break the project into three pieces: database storage/retrieval, HTML rendering, and the user interface.

The HTML rendering is handled by the PageBuilder class, which takes an array of Post objects and generates all required Page objects (representing HTML files).

public virtual void CreatePages()
{
    if (this.Posts.Length < 1) return;
    this.Pages = new List<Page>();
    this.SinglePostPages = GenerateSingles();
    // for each category of Post of the Post(s) in this.Posts
    foreach(var post_batch in this.Posts.GroupBy(p => p.Category))
    {
        Queue<Post> category_group = new Queue<Post>(post_batch.OrderByDescending(p => p.Timestamp));
        NavCategory peek = category_group.Peek().Category;
        this.Pages.AddRange(GeneratePages(category_group, peek));
    }
}

protected virtual Page[] GeneratePages(Queue<Post> posts, NavCategory category)
{
    Int16 requiredPages = (Int16)Math.Ceiling((double)posts.Count() / (double)this.PostsPerPage);
    Page[] generatedPages = new Page[requiredPages];
    for (int pageI = 1; pageI <= requiredPages; pageI++)
    {
        List<Post> newPosts = new List<Post>();
        for (int spaceRemaining = this.PostsPerPage; (spaceRemaining > 0) && (posts.Count() > 0); spaceRemaining--)
            newPosts.Add(posts.Dequeue());
        generatedPages[pageI - 1] = new Page
            (
                newPosts,
                new Navigation(category),
                new Breadcrumbs
                (
                    MakeLinks(requiredPages, category),
                    pageI
                )
            );
        generatedPages[pageI - 1].Title = generatedPages[pageI - 1].GenerateTitle();
        generatedPages[pageI - 1].Hrefs = this.Hrefs;
    }
    return generatedPages;
}

All HTML files can then be rendered and saved by calling the PageBuilder's SavePages() method.