Generated by Middleman

If you're not a computer person, you may not be terribly interested in this particular bit. But back in the 90s when I first started developing my own websites, I hand-coded my own static HTML pages and uploaded them to my server. When I started developing software and websites for a living, I used the Microsoft .NET platform and a SQL Server database to create dynamic websites where the contents were stored in database tables rather than in separate files. Any time a user arrived at a web page, the server would have to do the work of determining which page it was, go ask the database for the necessary content sections, interweave those sections into the site's HTML template and send that out to the user's web browser. For my own personal sites, I opted to use PHP and MySQL – essentially doing the same things with slightly different technologies.

WordPress

At some point in or around 2012, maybe even 2011, I sort of got tired of the way I was having to manage my own blog with hand-coded PHP and a custom MySQL schema. By switching to WordPress – an increasingly ubiquitous, very customizable PHP- and MySQL-based website platform – I felt like I caved in a bit. But it had a pretty good content editor, it was very easy to add pages, it provided good controls for presenting content along with controls for getting to content, and it seemed to present well on mobile devices. If someone who would not consider himself or herself to be a "computer person" were to ask me about putting a blog on the web, WordPress is still the first place to which I'd direct them. It's just easy as far as technology things go. But I wouldn't necessarily say that to a fellow tech-savvy developer type.

Going Static

One day, I got an email from a programming site offering a free book about "static web generators". After having spent more than the past decade building dynamic database-driven web sites, I immediately cringed at the thought. But I was curious as to why "the old way of doing things" was suddenly the topic of a new discussion. I took advantage of the offer, started reading about the concept, and very quickly started investigating options.

The essential idea behind the new static movement is that static web pages are faster to serve than dynamic ones. A server doesn't have to interact with any server-side scripting language or any database to retrieve content. A server simply has to grab the HTML file and send it on its way. But the original way of handling static web pages could get tedious quickly. If you had to change a menu, or an entire site's layout or theme, you had to edit every file on the site. So the latest idea was to essentially move the server-side processing to a local computer, use individual text files as the content database, then have a program compose or compile an entire site from all of that information.

Being experimental by nature, and thinking this new approach to static web site was rather intriguing, I decided to give it a go. I'm running a Mac, so I already had Ruby installed on my machine. Initially, I was convinced that I would rebuild my personal blog using Jekyll, since that seemed to be the most blog-centric state site generator. So I installed it and built a basic site with a couple pages of content just to see how things worked.

Enter Middleman

But in my searching, I'd also come across Middleman, which seemed to be a static site generator geared more toward non-blog sites – which is what most of my customers have. That got me thinking about possibly transitioning some customer sites over to a Ruby/Middleman static platform from a .NET/SQL Server server-side platform. So I changed course a bit, installed Middleman and build a new site for a customer with that. Then I did a couple other smaller sites for existing customers.

I know that in this day and age, server disk capacity isn't really an issue. There is no need for me to be concerned about a site taking up too much space on a server because really, I don't have too many customers with sites larger than 75 MB or so. Many are down around the 35 MB level. But most of that space is taken up with actual proprietary code, some related to the website and some related to back-end business software, which may or may not be in use.

The first site I built using Middleman takes up a mere 5.5 MB of server space. All of the pages are static. Because the customer has changed phone numbers a couple of times over the past few years, I added the phone number to a configuration file. So any page that needs that phone number gets it from the config file during page generation. (And the fact is that every page references that because the phone number is in the site's header.) If the customer changes the phone number again after he gets a new sales rep or sells the company, I can change the value in a single file, rebuild the site and upload everything.

Not for Everyone

The static web site is not the solution for every customer or person who wants a website. If a non-technical person wants to get on the web, this is probably not the ideal solution for them because there is some mucking about with code and what not. But for someone managing a site that doesn't need information from a database, and where the content for most of the pages really doesn't change that often, then I think Middleman is probably a decent contender for your development time.

Extras

Directories

On my Mac, the ~/Sites/ directory is where I develop all my websites. For personal sites, I have folder names directly beneath that match the full domain name of any given site. Also directly under ~/Sites/, I have a directory called clients/. Under that, I have directories for the name of each client. And then under those directories, I have a folder for the full domain name of the respective customer's sites (some customers have more than one site). So basically, it looks like this:

~/Sites/
     clients/
         amazingproducts/
             amazingproducts.com/
             buythisnow.net/
         widgetmakers/
             widgetmakers.com/
     fishburn.me/
     someothersite.net/

A typical site folder will have the following structure:

projectsite.com/
     build/
     config.rb
     data/
         pages.yml
     Gemfile
     Gemfile.lock
     images-source/
         company-logo-source.ai
     source/
         .htaccess
         404.html.haml
         css/
             _variables.scss
             site.css.scss
         images/
             company-logo.svg
         index.html.haml
         js/
             all.js
         layouts/
             _footer.haml
             _head.haml
             _header.haml
             layout.haml

Middleman Configuration

Typically, when you're starting a new Middleman project, you can simply type:
   middleman init projectname
and it will run through some paces to put the necessarily files in place. But I had some things that I wanted to keep consistent across several sites, like some particular configuration details. For instance, by default, Middleman creates a stylesheets/ folder and a javascripts/ folder for stylesheets and javascript files, respectively (and rather obviously). But personally, I like those folders to be named css/ and js/, respectively. So essentially, I have a "base" version of the what Middleman creates, modified with my own preferences.

In the config.rb file, I have the following lines:

page 'google################.html', :directory_index => false

— don't change my Google site verification file into a directory (of course, I have my own number in place of the ################)

activate :directory_indexes

— use directories like /about/ rather than files like /about.html

config[:css_dir] = 'css'
config[:js_dir] = 'js'

— override the directory names for the stylesheet and javascript locations

Then in the configure :build do section, I have uncommented two "minify" lines which are commented by default, and I added the gzip line which is not present by default:

activate :minify_css
activate :minify_javascript
activate :gzip

Aliases

I like shortcuts for certain things, especially if I'm working at the command line in the Mac’s Terminal application. So I have created some shortcuts for my Middleman workflow. In my ~/.profile file, I have created two aliases:

alias mmserve='bundle exec middleman serve'
alias mmbuild='bundle exec middleman build'

So from the base directory of the site I'm editing, let's say ~/Sites/middleman/, I can type mmserve (actually just mms[TAB] and let the shell autocomplete the command for me since this is a real shortcut) to start the local middleman server which lets me view my work real-time at http://localhost:4567/. When I think I'm done editing things, I can hit Ctrl+C, then type mmbuild and the scripts will build my site.

Scripts

Since I have my own already-slightly customized base for what I want in my middleman sites, and I have a straightforward folder structure for client sites, I created a Perl script that lets me specify a customer name and a site name, then it just copies my base site to a new location. I didn't really need to go this far out of my way. But I never had gotten too deep into Perl, so I took a little time to get my feet wet there as well. So if you're a Perl guru, don't furrow your brow too deeply at this script:

#!/usr/bin/perl -w
# mminit
# - Script to initialize a Middleman static site by copying the codebase
#   from an existing directory (~/Sites/middlemanbase/) to a directory
#   based on the site name specified by the user.
use 5.18.2;
# set the path for our Middleman codebase
use constant MIDDLEMAN_PATH => '/Users/fishburn/Sites/middlemanbase/';
# set up our main variables
my $argc = $#ARGV + 1;
my $client = '';
my $site = '';
# say_usage
# - We will use this to display the usage of this in case we don't get the
#   the number of parameters we're expecting
sub say_usage {
  say 'usage: mminit -c client_name -s domain_name';
  say '       mminit client_name domain_name';
  say '       mminit domain_name';
}
# assign_site
# - If the parameter passed matches a basic regular expression for a proper
#   domain name (including top-level domain), e.g. awesomesite.com, then we
#   assign our $site variable name accordingly. Otherwise, we send a message
#   to the user.
sub assign_site {
  if ($_[0] =~ /^[a-z0-9\.0]+\.\w{2,4}$/) {
  	$site = $_[0];
  } else {
  	say 'Invalid domain name. Expecting ';
  	&say_usage;
  	exit;
  }
}
# makedir
# - Attempt to create the directory specified by the parameter
sub makedir {
  mkdir $_[0], 0755 or warn "Cannot make $_[0] directory: $!";
}
# We check for parameters in reverse order of the syntax specified in say_usage
# (only because that order goes from simple and most likely used to longer and
# less likely used. But the parameters expected line up, respectively:
#   1 = domain_name
#   2 = client_name domain_name
#   4 = -c client_name -s domain_name
if ($argc == 1 || $argc == 2 || $argc == 4) {
  my $next = '';
  my $i = 0;
  if ($argc == 1) {
  	assign_site($ARGV[0]);
  } elsif ($argc == 2) {
  	$client = $ARGV[0];
  	&assign_site($ARGV[1]);
  } elsif ($argc == 4) {
  	for ($i = 0; $i < $argc; $i++) {
  		$_ = $ARGV[$i];
  		$next = $ARGV[$i + 1];
  		if (/^-c$/ && $next !~ m/^-/) {
  			$client = $next;
  			$i++;
  		}
  		elsif (/^-s$/ && $next !~ m/^-/) {
  			&assign_site($next);
  			$i++;
  		}
  	}
  }
  if ($site) {
  	if ($client) {
  		print "Initialize site at $client/$site?";
  	} else {
  		print "Initialize site at ./$site?";
  	}
 
  	print ' [yn] ';
  	chomp($_ = );
 
  	if ($_ eq 'y') {
  		if ($client) {
  			if (!-d $client) {
  				makedir($client);
  			}
  			chdir $client or die "cannot chdir to $client: $!";
  		}
 
  		if (!-d $site) {
  			makedir($site);
  			system('cp', '-a', MIDDLEMAN_PATH, $site);
  		}
  	} else {
  		say 'Exiting...';
  	}
  } else {
  	&say_usage;
  }
} else {
  &say_usage;
}

Then, I wanted a way to deploy a site to the proper server location with as few steps as possible. The typical method of moving files to a server is to use FTP. But I decided to try a different method and use rsync and another custom Perl script.

One quick note: on my web server, I have a folder specifically for uploading site previews so a client can see site changes without affecting the live site. For this folder, I forego a structure that includes client names, and have only directories for each specific site.

#!/usr/bin/perl -w
# mmdeploy
# - Script to deploy a Middleman static site to either a live or a preview
#	location on a remote server with an established folder structure convention.
use Cwd;
# set the base part of the server path
use constant SERVER_ROOT => "/my/server/root/folder";
# get an array of our current working directory
@paths = split /\//, cwd();
# set the site as the last part of the cwd (and pop it off the array)
$site = pop(@paths);
# set the client variable as the (new) last part of the cwd (and pop it off the array)
$client = pop(@paths);
print `clear`;
print "Push site to server\n";
print "===================\n";
# We should only proceed if our current folder is a direct child of "Sites",
# or if our current directory ends with a standard URL extension like ".com".
if ($site =~ /\.\w{3}$/ || $client eq 'Sites'){
  # this will be a user input option
  $o = '';
  # if we're not directly under "Sites", then show the $client
  if ($client ne 'Sites') {
    print "Client: " . $client . "\n";
  }
  # show the $site
  print "  Site: " . $site . "\n";
  # this just adds a line of "-" as long as the line above it
  print "-" x (8 + length($site)) . "\n";
  # only proceed if we have a "build" directory.
  if (-d 'build') {
    # loop until we get good input
    while ($o eq ''){
      print "Push to preview or live, or cancel? [plc] ";
      chomp($o = );
      # if we don't get 'p' or 'l', check for 'c'. If we didn't get 'c',
      # loop around and ask again. If we get 'c', exit.
      if ($o ne 'p' && $o ne 'l') {
        if ($o ne 'c') {
          $o = '';
        }
        else {
          exit;
        }
      }
      # if we get 'l', get confirmation because this is a big deal,
      # overwriting the live site with what is on the development side
      elsif ($o eq 'l') {
        print "Type 'live' to confirm push to live site: ";
        chomp($confirm = );
        # if the user doesn't type 'live', just exit.
        if ($confirm ne 'live') {
          exit;
        }
      }
    }
    # if our current directory is not directly under "Sites", then we must
    # be in a client folder. When we build the server path, we'll add
    # either $client or 'preview' depending on whether the user specified
    # live or preview, respectively
    if ($client ne 'Sites') {
      $rsync_path = join('/', "clients", ($o eq 'l' ? $client : 'preview'), $site);
    }
    # if we're directly under "Sites", then we're on one of our own sites,
    # and don't need to add $client. But we do need to still add "preview"
    # if necessary. If we're not on preview, the join() could potentially
    # double our "/" before the site name. So we'll keep
    else {
      $rsync_path = join('/', ($o eq 'p' ? 'preview' : ''), $site);
      # if we didn't add 'preview', we'll get '//' before $site. So we'll
      # us a regular expression to replace '//' with '/'.
      $rsync_path =~ s/\/\//\//;
    }
    $rsync_path = $rsync_path;
    print "deploy to /$rsync_path [yn]? ";
    chomp($o = );
    if ($o eq 'y') {
      system('rsync', '-a', '--exclude=.DS_Store', './build/', 'myserver:' . SERVER_ROOT . '/' . $rsync_path);
      if ($? == 0) {
        print "Success!\n";
      }
      else {
        printf "Failed! Exited with value %d\n", $? >> 8;
      }
    }
    else {
      print "--> exiting.\n";
      exit;
    }
  }
  else {
    print "** You must build the site first.\n";
  }
}
else {
  print "We don't seem to be in the proper subdirectory.\n";
  print "--> " . cwd() . "\n";
}