-
-
Save ColeTownsend/9281534 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#------------------------------------------------------------------------ | |
# encoding: utf-8 | |
# @(#)product_generator.rb 1.00 29-Nov-2011 16:38 | |
# | |
# Copyright (c) 2011 Jim Pravetz. All Rights Reserved. | |
# Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php) | |
# | |
# Description: A generator that creates product, products and | |
# ingredients pages for jekyll sites. Uses a JSON data | |
# file as the database file from which to read and | |
# generate the above files. | |
# | |
# Included filters : (none) | |
# | |
# Available _config.yml settings : | |
# - product_title_prefix An optional name to prefix product titles with (default '') | |
# - category_meta_description_prefix An optional product metadata prefix (default 'Product: ') | |
# - products_src_dir: The source subfolder from where product, products and ingredients pages are obtained. | |
# - products_dir: The subfolder to build products pages in (default is 'products'). | |
# - data_dir: The subfolder under source where data files are read. | |
# - data_product_file: The name of the JSON object file within the data_dir. | |
# | |
# Update History: (most recent first) | |
# 22-Dec-2011 jpravetz -- Added sitemap support | |
# 18-Dec-2011 jpravetz -- Added Page.to_liquid method to properly set page.url | |
# 5-Dec-2011 jpravetz -- Split product.options into an array | |
# 29-Nov-2011 jpravetz -- Created from category_generator.rb | |
#------------------------------------------------------------------------ | |
require 'json' | |
module Jekyll | |
# The ProductPage class creates a single ingredients, product, or products page | |
class ProductPage < Page | |
# The resultant relative URL of where the published file will end up | |
# Added for use by a sitemap generator | |
attr_accessor :dest_url | |
# The last modified date to be used for web caching of this file. | |
# Derived from latest date of products.json and template files | |
# Added for use by a sitemap generator | |
attr_accessor :src_mtime | |
# Initialize a new Page. | |
# | |
# site - The Site object. | |
# base - The String path to the source. | |
# dest_dir - The String path between the dest and the file. | |
# dest_name - The String name of the destination file (e.g. index.html or myproduct.html) | |
# src_dir - The String path between the source and the file. | |
# src_name - The String filename of the source page file, minus the markdown or html extension | |
# data_mtime - mtime of the products.json data file, used for sitemap generator | |
def initialize(site, base, dest_dir, dest_name, src_dir, src_name, data_mtime ) | |
@site = site | |
@base = base | |
@dir = dest_dir | |
@dest_dir = dest_dir | |
@dest_name = dest_name | |
@dest_url = File.join( '/', dest_dir ) | |
@dest_url = File.join( '/', dest_dir, dest_name ) if !dest_name.match( /index.html/i ) | |
@src_mtime = data_mtime | |
src_file = File.join(base, src_dir, "#{src_name}.markdown" ) | |
src_name_with_ext = "#{src_name}.markdown" if File.exists?( src_file ) | |
src_name_with_ext ||= "#{src_name}.html" | |
@name = src_name_with_ext | |
self.process(src_name_with_ext) | |
# Read the YAML from the specified page | |
self.read_yaml(File.join(base, src_dir), src_name_with_ext ) | |
# Remember the mod time, used for site_map | |
file_mtime = File.mtime( File.join(base, src_dir, src_name_with_ext) ) | |
@src_mtime = file_mtime if file_mtime > @src_mtime | |
end | |
# Override to set url properly | |
def to_liquid | |
self.data.deep_merge({ | |
"url" => @dest_url, | |
"content" => self.content }) | |
end | |
# Attach our data to the global page variable. This allows pages to see this data. | |
# Use to set ingredients or products. | |
def set_data( label, data ) | |
self.data[label] = data | |
end | |
# Attach our data to the global page variable. This allows pages to see this data. | |
# Use to set product. Also sets the page title. | |
def set_product_data( product, prev_product, next_product ) | |
self.data['product'] = product | |
self.data['product_prev'] = prev_product | |
self.data['product_next'] = next_product | |
title = product['title'] | |
# Set the title for this page. | |
title_prefix = site.config['product_title_prefix'] || '' | |
self.data['title'] = "#{title_prefix}#{title}" | |
# Set the meta-description for this page. | |
meta_description_prefix = site.config['category_meta_description_prefix'] || 'Product: ' | |
self.data['description'] = "#{meta_description_prefix}#{product['title']}" | |
end | |
# Override so that we can control where the destination file goes | |
def destination(dest) | |
# The url needs to be unescaped in order to preserve the correct filename. | |
path = File.join(dest, @dest_dir, @dest_name ) | |
path = File.join(path, "index.html") if self.url =~ /\/$/ | |
path | |
end | |
end | |
# The Site class is a built-in Jekyll class with access to global site config information. | |
# It is not necessary to extend the Site class, it just convenient to do so. And | |
# category_generator.rb did this, so it must be a good idea. | |
class Site | |
# Creates instances of ProductPage, renders then, and writes the output to a file. | |
# Will create a page for products index, ingredients index and each product. | |
def write_all_product_files | |
# Read the JSON file. This is our 'database'. We read obtain | |
# this object by running a command (hooked up as a Rake task) to | |
# read the data from a google spreadsheet via Google's APIs. | |
json_filename = self.config['data_product_file'] # || 'products.json' | |
data_hash = read_data_object( json_filename ) # if File.exists?( json_filename ) | |
json_mtime = data_hash['mtime'] if data_hash | |
data = data_hash['data'] if data_hash | |
ingredients = data['ingredients'] if data | |
products = data['products'] if data | |
unless ingredients && products | |
return "Products or Ingredients not found in data file" | |
end | |
puts "## Products file read: found #{products.length} products and #{ingredients.length} ingredients" | |
# Translate any values that need translating before we pass the data off to liquid processing | |
# new_products = clean_products( products ) | |
# This folder contains three source pages: ingredients, | |
# products, product. These three pages are used to build the | |
# public pages and can have a markdown or html extension | |
products_src_dir = self.config['products_src_dir'] || '_products' | |
# Write out all our pages | |
write_ingredients_index( ingredients, products_src_dir, 'ingredients', json_mtime ) | |
write_products_index( products, products_src_dir, 'products', json_mtime ) | |
write_product_indexes( products, products_src_dir, 'products', json_mtime ) | |
end | |
# Write an ingredients/index.html page | |
def write_ingredients_index( data, products_src_dir, dest_dir, data_mtime ) | |
index = ProductPage.new( self, self.config['source'], dest_dir, 'index.html', products_src_dir, 'ingredients', data_mtime ) | |
index.set_data( 'ingredients', data ) | |
index.render(self.layouts, site_payload) | |
# puts "## self.dest = #{self.dest}" | |
index.write(self.dest) | |
# Record the fact that this page has been added, otherwise Site::cleanup will remove it. | |
self.pages << index | |
end | |
# Write a products/index.html page | |
def write_products_index( data, products_src_dir, dest_dir, data_mtime ) | |
index = ProductPage.new( self, self.config['source'], dest_dir, 'index.html', products_src_dir, 'products', data_mtime ) | |
index.set_data( 'products', data ) | |
index.render(self.layouts, site_payload) | |
index.write(self.dest) | |
# Record the fact that this page has been added, otherwise Site::cleanup will remove it. | |
self.pages << index | |
end | |
# Loops through the list of product pages and processes each one. | |
def write_product_indexes( products, products_dir, dest_dir, data_mtime ) | |
if products && products.length > 0 | |
if self.layouts.key? 'page' | |
products.each_with_index do |product,index| | |
write_product_page( product, products_dir, dest_dir, (index > 0 ) ? products[index-1] : nil, products[index+1], data_mtime ) if product['publish'] | |
end | |
else | |
throw "No 'product' layout found." | |
end | |
end | |
end | |
# Write a products/product-name/index.html page | |
def write_product_page( product, products_src_dir, dest_dir, prev_product, next_product, data_mtime ) | |
# Attach our product data to global site variable. This allows pages to see this product's data. | |
puts "## Processing product #{product['id']}" | |
# puts "## Previous/next products: #{prev_product ? prev_product['id'] : 'nil'}/#{next_product ? next_product['id'] : 'nil'}" | |
index = ProductPage.new( self, self.config['source'], File.join(dest_dir,product['id']), 'index.html', products_src_dir, 'product', data_mtime ) | |
index.set_product_data( product, prev_product, next_product ) | |
index.render(self.layouts, site_payload) | |
index.write(self.dest) | |
# Record the fact that this page has been added, otherwise Site::cleanup will remove it. | |
self.pages << index | |
end | |
# Read and parse the JSON file under the data directory | |
# +filename+ is the String name of the file to be read | |
def read_data_object( filename ) | |
data_dir = self.config['data_dir'] || '_data' | |
data_path = File.join(self.config['source'], data_dir) | |
if File.symlink?(data_path) | |
return "Data directory '#{data_path}' cannot be a symlink" | |
end | |
file = File.join(data_path, filename) | |
return "File #{file} could not be found" if !File.exists?( file ) | |
result = nil | |
Dir.chdir(data_path) do | |
result = File.read( filename ) | |
end | |
puts "## Error: No data in #{file}" if result.nil? | |
# puts result | |
result = JSON.parse( result ) if result | |
{ 'data' => result, | |
'mtime' => File.mtime(file) } | |
end | |
end | |
# Jekyll hook - the generate method is called by jekyll, and generates all of the product pages. | |
class ProductGenerator < Generator | |
safe true | |
def generate(site) | |
site.write_all_product_files | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment