Challenge Accepted: Learn a bunch of new programming languages

I'm giving myself a challenge

Over the next year (and hopefully in less time than that) I'm going to teach myself at least 4 new programming languages. "That shouldn't be too hard", I say to myself confidently. After all, I already know the basics of programming, and generally feel like I have a good handle on it. I guess the question that most people ask themselves right about now is why. Why would I want to learn this many new languages? Do I have something big that I'm working in that requires this new knowledge? No, not really. Of course, I have ideas for some small side projects tucked away that I could make in these new languages. But there's certainly no requirement. If I wanted I could probably create all of them in C, Objective-C or Java, my best languages (or yes maybe scheme if I could remember how to do complicated things in it). Am I preparing for a job or to search for a new job? Nope. I've just started a job and I'm happy where I am. I actually just learned two new languages on the job, so that was cool. "Alright then, tell me the reason."

Because I think it's important to never stop learning. I've always been a curious person, or at least I'd like to think so. I was the type of kid that took all his toys apart to see what was going on inside them. And being so curious, I can't help but wonder what other programming languages and other technologies are like. It's a bit of a circular dependency. I'm curious because I think it's important to always learn new things, and I always want to learn new things because I'm curious. But I digress.

On of my main goals when picking languages to learn has to do with the web. I've never built anything for the web other than some static HTML pages. The way the world is going, at some point I'm going to have to learn how to write web apps, it's just a fact of life. I also haven't spent much time doing functional programming. When I learned Scheme and FP in college I really disliked it. I was an object oriented programmer through and through when I first started, but now I'm starting to see merit in other approaches. And finally, my favorite part of learning a new language is learning about the design patterns that go along or play nicely with that language. I didn't know anything about MVC before I taught myself Objective-C, but I consider myself a better programmer for having learned it. There are lots of interesting patterns out there, and I'm excited to dig into them as I learn these new languages.

In no particular order, here are the languages I think I'd like to to learn.

  • Ruby: this ties in a lot with the web requirement, but is also a great scripting language that I tend to see all over the place now.
  • JavaScript: if you want to develop for the web you're going to need to know JS, so I'd better pick this up.
  • PHP: I would consider PHP one of the lingua francas of the web. It's been around forever and it's likely to stick around too
  • Haskell: I hesitate putting this on here because it's so far out of left field from what I normally do. Haskell probably will give me the most things to learn though, so I think it's a good choice.
  • Any and a bunch of the frameworks built on top of JavaScript: there are a lot of great frameworks out there like Angular and Ember that can do some interesting things on top of JS. And of course there's jQuery
  • Hack: after learning PHP I think it would be interesting to learn about Facebook's new language as well. If it takes away some of the messiness of PHP I bet it would be useful
  • Go: with the promise of "C done right" who could stay away? I'm not a great C programmer, but I might be a good one. If Go really takes off it would be an awesome thing to know.
  • Rust: I'm not sure what rust is really for, but it sounds cool

NYT: Old Guard vs New Guard

Earlier this week, the New York Times posted an article titled Silicon Valley's Youth Problem which I have been thinking about a lot this week. As you can imagine the story quickly hit the front pages of reddit and Hacker News. As I browsed through the comments on both sites, I found a similar theme winding through both threads. "Why aren't people out there solving hard problems?" And that's something that is echoed in the article and is seen as the core problem of Silicon Valley and startup culture. People are dropping out of school to build things like Snapchat and WhatsApp, which do nothing to advance human society, like technology should. And things like that are where articles and comment threads start to lose me. They're right, and I know they're right. Human society and culture as a whole is not going to advance because someone built an application that lets you share photos and videos that self destruct. No one ever thought that it would. But is that really always the point? Is the only reason to get up in the morning and build something because it will change the course of human history? Didn't think so. This is something that I heard a lot of in my last semester of college also. Lots of times graduate students would come downstairs and work in the undergrad computer science lab, because we were all friends and whatnot. And occasionally people would get into pretty heated arguments about how startups "aren't solving any of the problems that matter", and that "if they were doing something hard I would take them more seriously." Which is all fine and good, but I find it to be a stupid comment. In my mind, there is one (maybe two) reason why people get involved in software development, and that is because they enjoy it. Programmers like to do what they do, because they enjoy building things. They enjoy seeing what they built improve the lives of others in whatever small way it can. And along with the enjoyment of building things comes the idea that you should build something that you enjoy buildings.

To me this is already a solved problem. If you get a bunch of software developers in a room and tell them to build something that they don't want to build, you'll probably come out with crappy code, and it will probably take a long time. That's just the way it works. Similarly, when you take a bunch of developers, put them in a room and ask them to build a project that they are really passionate about, you'll most likely get better code quality and it will take less time. So the answer to the question "Why aren't young developers solving hard problems?", because they aren't passionate about those problems. To be perfectly honest, as a young developer myself, I don't even know what those problems are. They most likely aren't things that I would come across in my day to day life, and sit back and figure out how to fix it. I could see myself running into the problem of wanting to send a picture message the self destructs after 10 seconds however. (I personally didn't see this problem, but I can see how someone else got there).

The other thing I saw a lot of in the comments was "Young talented developers are following the money." Which I think is only half true. It may be the case now, that people want to work at startups because they're getting $19B buyouts, but that wasn't the case when WhatsApp was founded.

For me at least, it came down to company culture. And that's what really comes to mind when I think "Old guard" and "new guard" of software. I didn't wind up working for a startup, but I did consider it for a while. There's something appealing about the idea of coming in to work every day wearing whatever you want, surrounded by a bunch of 20-somethings, who are all passionate about what they're doing. Contrast that to the classic corporate picture, where no one is having fun, and lots of people don't care about the end product. Maybe they don't care because the end product has been around for 30 years. Maybe they don't care because eventually the company policies and procedures have really beaten them down. Or maybe I'm stuck in the 1984 Apple commercial, who knows.

The point that I'm trying to make is that developers who are just out of college want their workplaces to be fun. They want to feel like they're in control of their own work, and they want to feel like they have a say in the future of the company. I'm not sure that anyone who works at a startup truly believes that their work is going to change the world in a meaningful and lasting manner. There are people out there who think that we should be "solving the hard problems because they're hard", and to them I say go for it! If solving a difficult problem is what it takes for you to feel like your work is worthwhile, then that's what you should be doing. Just don't hate on people who see things a bit differently. I happen to think that you should solve a problem because you want to, because it affects you, or because you care about those who it affects. That's the best way to get good quality software, and that's the best way to move forward.

Faking Git using Core Data and the Github API

As far as personal blogs and websites go, I'm a pretty big fan of Jekyll. I think that for people who are comfortable using the command line, there's really no reason to go with anything else. I also really like the fact that you can use Github to host and build your site. But I also think that if you use Jekyll to build your site, you should be able to work on it on your iPhone or iPad too, which is obviously why I'm trying to build Staticly (shameless plug, that post isn't really relevant to the cool things in this post).

Anyways, There were a few options to pick from to work with Github in order to work with jekyll sites. I could work with libgit/objective-git which would have been a good choice, and is one that I will probably go with if I need to support non-github repositories (which I probably will). I could use OctoKit which is the Github provided library for working with their API. Or I could roll my own and use AFNetworking to make the API Requests.

Obviously I went with option number three, otherwise I wouldn't be writing this post. Why? Well because reasons and stuff

  • I thought that it would be easier to not use the local filesystem for this project. It's much easier to query the database to get the correct file than having to deal with constructing file paths. This way I can get every file that exists in a specific directory (which is kinda important when you're working with jekyll) without having to use NSFileManager.

  • I also prefer to work with my own objects when I can. Should I get used to working with other people's code? Yes. Am I going to do it right now? Probably not

  • OctoKit is nice, but I haven't totally wrapped my head around Reactive Cocoa which OctoKit is built on.

  • Offline performance in mobile applications is very important to me. I always feel that people should be able to do work without a network connection, which means that I want my things persisted to disk. I'm most familiar with using Core Data for this (despite the numerous posts on the internet telling me not to).

  • Sometimes it's fun and a great learning experience to build out a lot of things yourself.

In order to really sink my teeth into the project, and to figure out how to replicate the git database structure in my Core Data design, I spent a lot of time going through the git documentation. Specifically Chapter 9 of the git book. I must say, I learned a whole lot about the way that files are stored in git by reading this, and I might even say that it made me a better git user in the process. But enough rambling. Instead of posting the headers, I'll just post the current state of the Core Data Object Model:

{<1 data-preserve-html-node="true">}!Graph View of the Core Data Managed Object Model

I've created a class called SLGithubSessionManager which is a subclass of AFHTTPSessionManager which handles setting up AFNetworking to use the Github API.

//
//  SLGithubSessionManager.h
//  Staticly
//
//  Created by Bradley Ringel on 1/3/14.
//  Copyright (c) 2014 Bradley Ringel. All rights reserved.
//

#import "AFHTTPSessionManager.h"
#import "SLUser.h"
#import "SLSite.h"
#import "SLCommit.h"
#import "SLTree.h"
#import "SLBlob.h"

@interface SLGithubSessionManager : AFHTTPSessionManager

@property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (strong, nonatomic) NSString *clientID;
@property (strong, nonatomic) NSString *clientSecret;

+ (instancetype)sharedManager;
- (SLUser *)currentUser;
- (SLSite *)currentSite;

- (SLCommit *)commitWithSha:(NSString *)sha;
- (SLTree *)treeWithSha:(NSString *)sha;
- (SLBlob *)blobWithSha:(NSString *)sha;

@end

SLGithubSessionManager Header file

//
//  SLGithubSessionManager.m
//  Staticly
//
//  Created by Bradley Ringel on 1/3/14.
//  Copyright (c) 2014 Bradley Ringel. All rights reserved.
//

#import "SLGithubSessionManager.h"
#import "SLAppDelegate.h"
@interface SLGithubSessionManager()

@end

@implementation SLGithubSessionManager

- (id)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration{
    self = [super initWithBaseURL:url sessionConfiguration:configuration];
    if(self){
        NSDictionary *apiInformation = [[NSDictionary alloc] initWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"githubapi" withExtension:@"plist"]];
        self.clientID = [apiInformation objectForKey:@"clientID"];
        self.clientSecret = [apiInformation objectForKey:@"clientSecret"];
        self.requestSerializer = [AFJSONRequestSerializer serializer];
        self.responseSerializer = [AFJSONResponseSerializer serializer];
        [self.requestSerializer setValue:@"application/vnd.github.v3+json" forHTTPHeaderField:@"Accept"];
        self.managedObjectContext = [(SLAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
    }

    return self;
}

+ (instancetype)sharedManager{

    static SLGithubSessionManager *manager;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
        configuration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
        manager = [[SLGithubSessionManager alloc] initWithBaseURL:[[NSURL alloc] initWithString:@"https://api.github.com"] sessionConfiguration:configuration];

    });

    return manager;
}

- (SLUser *)currentUser{
    NSPredicate *currentPredicate = [NSPredicate predicateWithFormat:@"currentUser == %@", @(YES)];
    NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"SLUser"];
    request.predicate = currentPredicate;

    NSError *error;
    NSArray *users = [self.managedObjectContext executeFetchRequest:request error:&error];

    if(users.count == 1){
        //if this isn't the case then we've got a problem
        return [users firstObject];
    }
    else{
        return nil;
    }
}

- (SLSite *)currentSite{
    NSPredicate *currentPredicate = [NSPredicate predicateWithFormat:@"currentSite == %@", @(YES)];
    NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"SLSite"];
    request.predicate = currentPredicate;

    NSError *error;
    NSArray *sites = [self.managedObjectContext executeFetchRequest:request error:&error];

    if(sites.count == 1){
        //if this isn't the case then we've got a problem
        return [sites firstObject];
    }
    else{
        return nil;
    }
}

- (SLCommit *)commitWithSha:(NSString *)sha{
    NSPredicate *pred = [NSPredicate predicateWithFormat:@"sha == %@", sha];
    NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"SLCommit"];
    request.predicate = pred;
    NSError *error;

    NSArray *commits = [self.managedObjectContext executeFetchRequest:request error:&error];
    if(commits.count == 1){
        //i hope that this is the case
        return [commits firstObject];
    }
    else{
        return nil;
    }
}

- (SLTree *)treeWithSha:(NSString *)sha{
    NSPredicate *pred = [NSPredicate predicateWithFormat:@"sha == %@", sha];
    NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"SLTree"];
    request.predicate = pred;
    NSError *error;

    NSArray *trees = [self.managedObjectContext executeFetchRequest:request error:&error];
    if(trees.count == 1){
        return [trees firstObject];
    }
    else{
        return nil;
    }
}

- (SLBlob *)blobWithSha:(NSString *)sha{
    NSPredicate *pred = [NSPredicate predicateWithFormat:@"sha == %@", sha];
    NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"SLBlob"];
    request.predicate = pred;
    NSError *error;

    NSArray *blobs = [self.managedObjectContext executeFetchRequest:request error:&error];
    if(blobs.count == 1){
        return [blobs firstObject];
    }
    else{
        return nil;
    }
}

@end

SLGithubSessionManager Implementation File

You can see that I've got SLGithubSessionManager doing a few other things to make life easier. In order to prevent creating duplicate objects when they come in from Github, the commitWithSha:, treeWithSha: and blobWithSha: will return the appropriate object if one exists. Since every time I'm dealing with objects that may or may not exist in my database already, there is an SLGithubSessionManager around, I figured this would be the easiest place to access those convenience methods.

Now on to the fun stuff. I'll skip logging in to Github and getting the OAuth token because that's old news.

Here's how you get all of the authenticated users repositories:

NSString *fetchString = [NSString stringWithFormat:@"/users/%@/repos", self.currentUser.username];

SLGithubSessionManager *manager = [SLGithubSessionManager sharedManager];

[manager GET:fetchString parameters:@{@"access_token" : self.currentUser.oauthToken} success:^(NSURLSessionDataTask *task, id responseObject) { NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response; if(response.statusCode == 200){ NSArray *responseData = responseObject; self.sites = [responseData copy];

         [self.tableView reloadData];

     }
 }
 failure:^(NSURLSessionDataTask *task, NSError *error) {

 }];

The API returns JSON, which AFNetworking then serializes into an array of NSDictionary objects, called responseObject. To avoid creating an SLSite object for each repository that is returned, I simply use the dictionary to show that information to the user, and then create the object once the user has selected a row in a UITableView.

The method that fetches the branches of the selected repository is pretty much identical, so I won't post that.

Here's the best part, cloning an entire repository into a Core Data database:

NSPredicate *currentPredicate = [NSPredicate predicateWithFormat:@"defaultBranch == %@", @(YES)];
NSOrderedSet *defaultBranch = [[[[SLGithubSessionManager sharedManager] currentSite] branches] filteredOrderedSetUsingPredicate:currentPredicate];
SLCommit *head = [[defaultBranch firstObject] commit];

SLGithubSessionManager *manager = [SLGithubSessionManager sharedManager];
NSString *username = [[manager currentUser] username];
NSString *siteName = [[manager currentSite] name];
NSString *token = [[manager currentUser] oauthToken];

void (^blobSuccessBlock)(NSURLSessionDataTask *, id) = ^void (NSURLSessionDataTask * task, id responseObject){
    NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
    if(response.statusCode == 200){
        NSDictionary *blobData = responseObject;
        SLBlob *blob = [manager blobWithSha:[blobData objectForKey:@"sha"]];
        blob.content = [[NSData alloc] initWithBase64EncodedString:[blobData objectForKey:@"content"] options:NSDataBase64DecodingIgnoreUnknownCharacters];
        NSError *error;
        [self.managedObjectContext save:&error];
    }
};

void (^blobFailBlock)(NSURLSessionDataTask *, NSError *) = ^void (NSURLSessionDataTask *task, NSError *error){
    NSLog(@"%@", error);
};
void (^treeFailBlock)(NSURLSessionDataTask *, NSError *) = ^void (NSURLSessionDataTask *task, NSError *error){
    NSLog(@"%@", error);
};

void (^__block treeSuccessBlock)(NSURLSessionDataTask *, id) = ^void (NSURLSessionDataTask *task, id responseObject){
    NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;

    if(response.statusCode == 200){
        NSDictionary *treeData = responseObject;
        NSArray *objects = [treeData objectForKey:@"tree"];
        SLTree *rootTree = [manager treeWithSha:[treeData objectForKey:@"sha"]];
        for(NSDictionary *gitObject in objects){
            //Check to see whether this is a tree or a blob, and do the right thing
            //dont forget to set the relationship in all of them
            if([[gitObject objectForKey:@"type"] isEqualToString:@"tree"]){
                NSFetchRequest *treeRequest = [[NSFetchRequest alloc] initWithEntityName:@"SLTree"];
                NSPredicate *treeShaPredicate = [NSPredicate predicateWithFormat:@"%K LIKE %@", @"sha", [gitObject objectForKey:@"sha"]];
                treeRequest.predicate = treeShaPredicate;
                NSError *error;
                NSArray *trees = [self.managedObjectContext executeFetchRequest:treeRequest error:&error];

                SLTree *tree;
                if(trees.count == 0){
                    tree = [NSEntityDescription insertNewObjectForEntityForName:@"SLTree" inManagedObjectContext:self.managedObjectContext];
                }
                else{
                    tree = [trees firstObject];
                }
                tree.sha = [gitObject objectForKey:@"sha"];
                tree.path = [gitObject objectForKey:@"path"];
                tree.parent = rootTree;

                [self.managedObjectContext save:&error];
                NSString *treeGetString = [NSString stringWithFormat:@"/repos/%@/%@/git/trees/%@",username,siteName,tree.sha];
                [manager GET:treeGetString parameters:@{@"access_token" : token} success:treeSuccessBlock failure:treeFailBlock];
            }
            else{
                NSFetchRequest *blobRequest = [[NSFetchRequest alloc] initWithEntityName:@"SLBlob"];
                NSPredicate *blobShaPredicate = [NSPredicate predicateWithFormat:@"%K LIKE %@", @"sha", [gitObject objectForKey:@"sha"]];
                blobRequest.predicate = blobShaPredicate;
                NSError *error;
                NSArray *blobs = [self.managedObjectContext executeFetchRequest:blobRequest error:&error];

                SLBlob *blob;
                if(blobs.count == 0){
                    blob = [NSEntityDescription insertNewObjectForEntityForName:@"SLBlob" inManagedObjectContext:self.managedObjectContext];
                }
                else{
                    blob = [blobs firstObject];
                }
                blob.sha = [gitObject objectForKey:@"sha"];
                blob.path = [gitObject objectForKey:@"path"];
                blob.tree = rootTree;

                [self.managedObjectContext save:&error];

                NSString *blobGetString = [NSString stringWithFormat:@"/repos/%@/%@/git/blobs/%@", username, siteName, blob.sha];
                [manager GET:blobGetString parameters:@{@"access_token" : token, @"encoding" : @"base64"} success:blobSuccessBlock failure:blobFailBlock];
            }
        }
    }
};



void (^commitSuccessBlock)(NSURLSessionDataTask *, id) = ^void (NSURLSessionDataTask *task, id responseObject){
    NSError *error;

    NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
    if(response.statusCode == 200){
        NSDictionary *commitData = responseObject;
        //loop through all the parents and get those
        for(NSDictionary *parent in [commitData objectForKey:@"parents"]){
            NSFetchRequest *commitRequest = [[NSFetchRequest alloc] initWithEntityName:@"SLCommit"];
            NSPredicate *commitShaPredicate = [NSPredicate predicateWithFormat:@"%K LIKE %@", @"sha", [parent objectForKey:@"sha"]];
            commitRequest.predicate = commitShaPredicate;

            NSArray *commits = [self.managedObjectContext executeFetchRequest:commitRequest error:&error];

            SLCommit *p;
            if(commits.count == 0){
                p = [NSEntityDescription insertNewObjectForEntityForName:@"SLCommit" inManagedObjectContext:self.managedObjectContext];
            }
            else{
                p = [commits firstObject];
            }
            p.sha = [parent objectForKey:@"sha"];
            p.url = [parent objectForKey:@"url"];

            p.child = head;
            [self.managedObjectContext save:&error];
        }
        //create a tree
        NSFetchRequest *treeRequest = [[NSFetchRequest alloc] initWithEntityName:@"SLTree"];
        NSPredicate *treeShaPredicate = [NSPredicate predicateWithFormat:@"%K LIKE %@", @"sha", [commitData valueForKeyPath:@"tree.sha"]];
        treeRequest.predicate = treeShaPredicate;
        NSError *error;
        NSArray *trees = [self.managedObjectContext executeFetchRequest:treeRequest error:&error];

        SLTree *tree;
        if(trees.count == 0){
            tree = [NSEntityDescription insertNewObjectForEntityForName:@"SLTree" inManagedObjectContext:self.managedObjectContext];
        }
        else{
            tree = [trees firstObject];
        }
        tree.url = [commitData valueForKeyPath:@"tree.url"];
        tree.sha = [commitData valueForKeyPath:@"tree.sha"];

        tree.commit = head;

        [self.managedObjectContext save:&error];

        //go and get that tree


        NSString *treeGetString = [NSString stringWithFormat:@"/repos/%@/%@/git/trees/%@",username,siteName,tree.sha];
        [manager GET:treeGetString parameters:@{@"access_token" : token} success:treeSuccessBlock failure:treeFailBlock];
    }
};

void (^commitFailBlock)(NSURLSessionDataTask *, NSError *) = ^void (NSURLSessionDataTask *task, NSError *error){
    NSLog(@"%@", error);
};

NSString *commitGetString = [NSString stringWithFormat:@"/repos/%@/%@/git/commits/%@", username, siteName, head.sha];

[manager GET:commitGetString parameters:@{@"access_token": token} success:commitSuccessBlock failure:commitFailBlock];

There is a lot of code here, but most of it is pretty simple. What actually kicks the process off is line 151, which will get the information for the root of the repository from Github. Once the request for the root tree comes back, we look through each of the objects in the response and check their type. If they are a tree, then we want to recursively use our treeSuccessBlock to get the rest of the data for that child tree. If we get to a blob, then we just want to create the entity and call it a day. Before we make another request to get the new data in either case we go ahead and set up the entity relationships.

And that's really all there is to it. It's not the most complicated bit of code in the world, but I thought it was neat that I could replicate a program that I'm familiar with using two frameworks (AFNetworking and Core Data) that I haven't spent that much time with. I'm planing on taking this further and using the API to make commits, and combining that with autosaving that should be pretty effortless to use.

Staticky Update Number 1

Since I have some down time, I figured that I would post an update on Staticly progress for anyone interested. Here's the initial post for anyone who is confused and saying "what in the world is Staticly?" Anyways, since graduating from Indiana University last month, I've had some free time until I move to my new place and start my new job. I decided to take the code that I had written during the month of November for Hack Month and basically throw it out the window. It's not that it wasn't good code, I've written much worse. I just felt that since I hadn't worked on the project in over a month, it would make more sense for me to really put the project back in my head by redesigning it. I've also been trying to prepare for the real world and go "design first". That means that classes don't get created until I've mapped them out a little bit. I'm also using this project to get back into using Trello as my to do list, just because it seemed like a good idea to switch to that at the same time as scraping the project.

Anyways, this is a progress update! So here's what's going on with Staticly right now.

  1. I'm not sure if this is covered in the original post, but I'm using AFNetworking instead of RestKit or OctoKit. Why? Because this seemed like as good of a time as any to learn how to do iOS networking with one of the most used networking frameworks in the ecosystem. I tried RestKit, and failed at it, mostly because the mapping was just a bit too much work for me to wrap my head around. Doing the JSON Deserialization myself hasn't been too bad, since each object only has a few properties that I care about right now. OctoKit looked nice, but the reactive nature of the framework wasn't something I wanted to dive into right now. Maybe for another project.

  2. I've decided to focus on getting the iPad version of the app working first. It seems to me that more people are likely to want to do this kind of editing on a larger screen (most likely with an external keyboard). Luckily, I bought myself a 1st gen iPad Mini over the summer, so I have something to do development. You want a screenshot? Of course you want a screenshot!

{<1 data-preserve-html-node="true">}!"She may not look like much, but she's got it where it counts, kid. I've made a lot of special modifications myself."

I know that it doesn't look like too much right now, but it represents a lot of hard work on my part. Basically, what I've done so far clone a git repository using only the Github API, AFNetworking and Core Data. Each of the files that I downloaded using the API is stored in the local database. What's cool about this to me is that I didn't have to use a git library or the local filesystem directly to do this. I've been planning on elaborating on this a bit more in a separate post eventually. Now, someone is saying "But brad, this method isn't portable because it only works with Github." Well person, you're right. This isn't portable at all. I'm banking on a lot of people using Github pages to host their site, because it is by far the easiest way that I've found. This approach also brings the benefits of Github's automatic page generation whenever you push a new commit to the repository. That means that I can focus on the application right now, and not worry too much about doing my own markdown/liquid/yaml parsing.

What's next?

So that's what's happening with Staticly right now, but what about what's coming next? Next up is the actual file editing. If you look at the screenshot again, you can see that all of the post metadata is stuck at the top of the page (if you're familiar with jekyll), and the whole view just looks basic. I'm hoping to work on a better interface for each post/page that is going to be edited, as well as a different (better?) way to look at the configuration file. After that the menu is going to be reworked, along with the overall UI scheme. After that, who knows?!

You can follow the project a few ways:

Github Repository

Trello Board for Development

Testflight Beta Tester sign up

For the last link, please sign up if you're interested in the project, and if you have an iPad and a Jekyll blog hosted on Github Pages. I could really use some outside input on this one.