├── VERSION ├── _layouts └── category_index.html ├── _plugins └── octopress_filters.rb ├── LICENSE ├── _includes └── custom │ └── category_feed.xml ├── generate_sitemap.rb ├── README.markdown ├── generate_categories.rb └── generate_projects.rb /VERSION: -------------------------------------------------------------------------------- 1 | 0.2.4 -------------------------------------------------------------------------------- /_layouts/category_index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 |

{{ page.title }}

6 | -------------------------------------------------------------------------------- /_plugins/octopress_filters.rb: -------------------------------------------------------------------------------- 1 | # Filters taken from the Octopress project by Brandon Mathis. 2 | # https://github.com/imathis/octopress/blob/master/plugins/octopress_filters.rb 3 | module Jekyll 4 | 5 | module Filters 6 | 7 | # Escapes CDATA sections in post content 8 | def cdata_escape(input) 9 | input.gsub(//, ']]>') 10 | end 11 | 12 | # Replaces relative urls with full urls 13 | def expand_urls(input, url='') 14 | url ||= '/' 15 | input.gsub /(\s+(href|src)\s*=\s*["|']{1})(\/[^\"'>]*)/ do 16 | $1+url+$3 17 | end 18 | end 19 | 20 | end 21 | 22 | end -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Dave Perrett, http://recursive-design.com/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /_includes/custom/category_feed.xml: -------------------------------------------------------------------------------- 1 | --- 2 | layout: nil 3 | --- 4 | 5 | 6 | 7 | <![CDATA[{{ page.title }} | {{ site.title }}]]> 8 | 9 | 10 | {{ site.time | date_to_xmlschema }} 11 | {{ site.url }}/ 12 | 13 | 14 | {% if site.email %}{% endif %} 15 | 16 | Recurser 17 | 18 | {% for post in site.categories[page.category] limit: 5 %} 19 | 20 | <![CDATA[{{ post.title | cdata_escape }}]]> 21 | 22 | {{ post.date | date_to_xmlschema }} 23 | {{ site.url }}{{ post.id }} 24 | 25 | 26 | {% endfor %} 27 | -------------------------------------------------------------------------------- /generate_sitemap.rb: -------------------------------------------------------------------------------- 1 | # Jekyll sitemap page generator. 2 | # http://recursive-design.com/projects/jekyll-plugins/ 3 | # 4 | # Version: 0.2.4 (201210160037) 5 | # 6 | # Copyright (c) 2010 Dave Perrett, http://recursive-design.com/ 7 | # Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php) 8 | # 9 | # A generator that creates a sitemap.xml page for jekyll sites, suitable for submission to 10 | # google etc. 11 | # 12 | # To use it, simply drop this script into the _plugins directory of your Jekyll site. 13 | # 14 | # When you compile your jekyll site, this plugin will loop through the list of pages in your 15 | # site, and generate an entry in sitemap.xml for each one. 16 | 17 | require 'pathname' 18 | 19 | module Jekyll 20 | 21 | 22 | # Monkey-patch an accessor for a page's containing folder, since 23 | # we need it to generate the sitemap. 24 | class Page 25 | def subfolder 26 | @dir 27 | end 28 | end 29 | 30 | 31 | # Sub-class Jekyll::StaticFile to allow recovery from unimportant exception 32 | # when writing the sitemap file. 33 | class StaticSitemapFile < StaticFile 34 | def write(dest) 35 | super(dest) rescue ArgumentError 36 | true 37 | end 38 | end 39 | 40 | 41 | # Generates a sitemap.xml file containing URLs of all pages and posts. 42 | class SitemapGenerator < Generator 43 | safe true 44 | priority :low 45 | 46 | # Generates the sitemap.xml file. 47 | # 48 | # +site+ is the global Site object. 49 | def generate(site) 50 | # Create the destination folder if necessary. 51 | site_folder = site.config['destination'] 52 | unless File.directory?(site_folder) 53 | p = Pathname.new(site_folder) 54 | p.mkdir 55 | end 56 | 57 | # Write the contents of sitemap.xml. 58 | File.open(File.join(site_folder, 'sitemap.xml'), 'w') do |f| 59 | f.write(generate_header()) 60 | f.write(generate_content(site)) 61 | f.write(generate_footer()) 62 | f.close 63 | end 64 | 65 | # Add a static file entry for the zip file, otherwise Site::cleanup will remove it. 66 | site.static_files << Jekyll::StaticSitemapFile.new(site, site.dest, '/', 'sitemap.xml') 67 | end 68 | 69 | private 70 | 71 | # Returns the XML header. 72 | def generate_header 73 | "\n" 74 | end 75 | 76 | # Returns a string containing the the XML entries. 77 | # 78 | # +site+ is the global Site object. 79 | def generate_content(site) 80 | result = '' 81 | 82 | # First, try to find any stand-alone pages. 83 | site.pages.each { |page| 84 | path = page.subfolder + '/' + page.name 85 | 86 | # Skip files that don't exist yet (e.g. paginator pages) 87 | next unless FileTest.exist?(path) 88 | 89 | mod_date = File.mtime(site.source + path) 90 | 91 | # Use the user-specified permalink if one is given. 92 | if page.permalink 93 | path = page.permalink 94 | else 95 | # Be smart about the output filename. 96 | path.gsub!(/.md$/, '.html') 97 | end 98 | 99 | # Ignore SASS, SCSS, and CSS files 100 | next if path =~ /.(sass|scss|css)$/ 101 | 102 | # Remove the trailing 'index.html' if there is one, and just output the folder name. 103 | path = path[0..-11] if path =~ /\/index.html$/ 104 | 105 | result += entry(path, mod_date, get_attrs(page), site) unless path =~ /error/ 106 | } 107 | 108 | # Next, find all the posts. 109 | posts = site.site_payload['site']['posts'] 110 | for post in posts do 111 | url = post.url 112 | url = '/' + url unless url =~ /^\// 113 | url = url[0..-11] if url=~/\/index.html$/ 114 | result += entry(url, post.date, get_attrs(post), site) 115 | end 116 | 117 | result 118 | end 119 | 120 | def get_attrs( page ) 121 | attrs = Hash.new 122 | attrs[:changefreq] = page.data['changefreq'] if page.data.has_key?('changefreq') 123 | attrs[:priority] = page.data['priority'] if page.data.has_key?('priority') 124 | attrs 125 | end 126 | 127 | # Returns the XML footer. 128 | def generate_footer 129 | "\n" 130 | end 131 | 132 | # Creates an XML entry from the given path and date. 133 | # 134 | # +path+ is the URL path to the page. 135 | # +date+ is the date the file was modified (in the case of regular pages), or published (for blog posts). 136 | # +changefreq+ is the frequency with which the page is expected to change (this information is used by 137 | # e.g. the Googlebot). This may be specified in the page's YAML front matter. If it is not set, nothing 138 | # is output for this property. 139 | def entry(path, date, attrs, site) 140 | # Remove the trailing slash from the baseurl if it is present, for consistency. 141 | baseurl = site.config['baseurl'] 142 | baseurl = baseurl[0..-2] if baseurl=~/\/$/ 143 | 144 | " 145 | 146 | #{baseurl}#{path} 147 | #{date.strftime("%Y-%m-%d")} 148 | " + attrs.map { |k,v| " <#{k}>#{v}" }.join("\n") + " 149 | " 150 | end 151 | 152 | end 153 | 154 | end 155 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | 2 | About 3 | ----- 4 | 5 | This is a collection of [Jekyll](https://github.com/mojombo/jekyll) plugins and generators that I've written for use on [daveperrett.com](http://daveperrett.com/). 6 | 7 | License 8 | ------- 9 | 10 | The plugins are distributed under the [MIT License](http://en.wikipedia.org/wiki/MIT_license). See the [License](https://github.com/recurser/jekyll-plugins/blob/master/LICENSE) file for details. 11 | 12 | Installation 13 | ------------ 14 | 15 | To install the plugins simply put them in a directory named _\_plugins_ in your project directory. 16 | 17 | Bug Reports 18 | ----------- 19 | 20 | If you come across any problems, please [create a ticket](https://github.com/recurser/jekyll-plugins/issues) and we'll try to get it fixed as soon as possible. 21 | 22 | Want to make your own plugins? 23 | ------------------------------ 24 | 25 | More information on the Jekyll plugin architecture is available from the [Jekyll wiki](https://github.com/mojombo/jekyll/wiki/Plugins). 26 | 27 | 28 | generate_projects.rb 29 | ==================== 30 | 31 | A generator that creates project pages for Jekyll sites from git repositories. 32 | 33 | This was inspired by the project pages on GitHub, which use the project _README_ file as the index page. It takes git repositories, and automatically builds project pages for them using the _README_ file, along with downloadable zipped copies of the projects themselves (for example, the project page for this [plugin repository](http://www.daveperrett.com/articles/2010/12/08/jekyll-plugins-for-categories-projects-and-sitemaps/) is auto-generated with this plugin). 34 | 35 | Usage 36 | ----- 37 | 38 | To use it, simply drop the _generate_projects.rb_ script into the _\_plugins_ directory of your Jekyll site. Next, create a _\_projects_ folder in the base of your Jekyll site. This folder should contain _.yml_ files describing how to build a page for your project. Here is an example _\_projects/jekyll-plugins.yml_): 39 | 40 | ``` yaml 41 | layout: default 42 | title: Jekyll Plugins 43 | repository: git://daveperrett.com/jekyll-plugins.git 44 | published: true 45 | ``` 46 | 47 | How it works 48 | ------------ 49 | 50 | When you compile your Jekyll site, the plugin will download the git repository of each project in your _\_projects_ folder, create an index page from the _README_ file (using the specified layout), and create a downloadable _.zip_ file of the project. The goal is to automate the construction of online project pages, keep them in sync with _README_ documentation, and provide an up-to-date zip archive for download. 51 | 52 | Required files 53 | -------------- 54 | 55 | Your project's git repository should contain: 56 | 57 | * _README_ : The contents of this will be used as the body of your project page will be created from. Any extension other than .markdown, .textile or .html will be treated as a .textile file. 58 | * _versions.txt_ : Contains the version string (eg 1.0.0). Used when naming the downloadable zip-file (optional). If the _version.txt_ file is not available, a _YYYYMMDDHHMM_ timestamp will be used for the version instead. 59 | 60 | Required gems 61 | ------------- 62 | 63 | * git (>= 1.2.5) 64 | * rubyzip (>= 0.9.4) 65 | 66 | Available \_config.yml settings 67 | ------------------------------ 68 | 69 | * _project_dir_ : The subfolder to compile projects to (default is 'projects'). 70 | 71 | Available YAML settings 72 | ----------------------- 73 | 74 | * _repository_ : Git repository of your project (required). 75 | * _layout_ : Layout to use when creating the project page. 76 | * _title_ : Project title, which can be accessed in the layout. 77 | * _published_ : Project won't be published if this is false. 78 | 79 | There is also an optional _zip_folder_name_ setting, in case you want the unzipped folder to be named 80 | something other than the project name. This is useful (for eaxmple) if you want it to unzip as an 81 | OS X 'Something.app' application bundle. 82 | 83 | 84 | generate_categories.rb 85 | ====================== 86 | 87 | A generator that creates category pages for Jekyll sites (for example our [plugin category](http://www.daveperrett.com/articles/categories/plugin/)). 88 | 89 | Usage 90 | ----- 91 | 92 | To use it, simply drop the _generate_categories.rb_ script into the _\_plugins_ directory of your Jekyll site. 93 | 94 | You should also copy the [category_index.html](https://github.com/recurser/jekyll-plugins/blob/master/_layouts/category_index.html) file to the _\_layouts_ directory of your own project. This file is provided as an example layout, and obviously you can change the HTML as you see fit. 95 | 96 | You can also (optionally) generate an _atom.xml_ feed for each category. To do this, copy the [category_feed.xml](https://github.com/recurser/jekyll-plugins/blob/master/_includes/custom/category_feed.xml) file to the _\_includes/custom_ directory of your own project. You'll also need to copy the [octopress_filters.rb](https://github.com/recurser/jekyll-plugins/blob/master/_plugins/octopress_filters.rb) file into the _\_plugins_ directory of your project, as the _category_feed.xml_ requires a couple of extra filters. 97 | 98 | How it works 99 | ------------ 100 | 101 | When you compile your Jekyll site, this plugin will loop through the list of categories in your site, and use the layout above to generate a page for each one with a list of links to the individual posts. 102 | 103 | Included filters 104 | ---------------- 105 | 106 | * _category_links_ : Outputs the list of categories as comma-separated links. 107 | * _date_to_html_string_ : Outputs the post.date as formatted html, with hooks for CSS styling. 108 | 109 | Available \_config.yml settings 110 | ------------------------------ 111 | 112 | * _category_dir_ : The subfolder to build category pages in (default is 'categories'). 113 | * _category_title_prefix_ : The string used before the category name in the page title (default is 'Category: '). 114 | 115 | 116 | generate_sitemap.rb 117 | =================== 118 | 119 | A simple generator that creates a _sitemap.xml_ page for Jekyll sites, suitable for submission to Google etc (for example the _sitemap.xml_ for [daveperrett.com](http://www.daveperrett.com/sitemap.xml). 120 | 121 | Usage 122 | ----- 123 | 124 | To use it, simply drop the _generate_sitemap.rb_ script into the _\_plugins_ directory of your Jekyll site. 125 | 126 | How it works 127 | ------------ 128 | 129 | When you compile your Jekyll site, the plugin will loop through the list of pages in your site, and generate an entry in _sitemap.xml_ for each one. 130 | 131 | Available YAML settings 132 | ----------------------- 133 | 134 | * _changefreq_ : How often this page will change. This setting is optional, but if specified its value must be one of `always`, `hourly`, `daily`, `weekly`, `monthly`, `yearly`, or `never`. See [the sitemap specification](http://www.sitemaps.org/protocol.php#xmlTagDefinitions) for more details on what this is used for. By default, this property is omitted for static pages and `never` for the files in `_posts` (since these are typically blog entries or the like). 135 | 136 | 137 | Change history 138 | ============== 139 | 140 | * **Version 0.2.1 (2012-10-15)** : Merged some updates from [Octopress](https://github.com/imathis/octopress/blob/master/plugins/category_generator.rb) back in. 141 | * Add support for _atom.xml_ feed generation for categories. 142 | * Improved handling of multibyte and multi-word category names in URLs. 143 | * **Version 0.2.0 (2012-10-14)** : 144 | * Add support for priority in _generate_sitemap.rb_ (thanks [hez](https://github.com/hez)!). 145 | * Remove hard-coded category directory in _generate_categories.rb_ (thanks [ghinda](https://github.com/ghinda) and [MrWerewolf](https://github.com/MrWerewolf)!). 146 | * Improved slash handling in _generate_sitemap.rb_. 147 | * **Version 0.1.8 (2011-08-15)** : A bunch of fixes and improvements (thanks [bdesham](https://github.com/bdesham)!). 148 | * **Version 0.1.7 (2011-07-19)** : Sitemap base URL fix (thanks [ojilles](https://github.com/ojilles)!). 149 | * **Version 0.1.6 (2011-05-21)** : Added optional _zip_folder_name_ YAML config setting. 150 | * **Version 0.1.5 (2011-05-21)** : Replace github-style code markup to pygments-compatible 'highlight' format. 151 | * **Version 0.1.4 (2011-05-08)** : Applied patch to fix permalink problem in _generate_sitemap.rb_ (thanks [ejel](https://github.com/ejel)!). 152 | * **Version 0.1.3 (2011-01-06)** : Fixed pygments code formatting bug introduced in _generate_projects.rb_ v0.1.2. 153 | * **Version 0.1.2 (2011-01-06)** : Add generated pages to the Site::pages list, to stop them being deleted automatically by Site::cleanup(); Fixed a file extension problem with _generate_projects.rb_. 154 | * **Version 0.1.1 (2010-12-10)** : Use _mtime_ instead of _ctime_ for sitemap modification dates; Fixed sitemap extension bug. 155 | * **Version 0.1.0 (2010-12-08)** : Initial release. 156 | 157 | 158 | Contributing 159 | ============ 160 | 161 | Once you've made your commits: 162 | 163 | 1. [Fork](http://help.github.com/fork-a-repo/) jekyll-plugins 164 | 2. Create a topic branch - `git checkout -b my_branch` 165 | 3. Push to your branch - `git push origin my_branch` 166 | 4. Create a [Pull Request](http://help.github.com/pull-requests/) from your branch 167 | 5. That's it! 168 | 169 | 170 | Author 171 | ====== 172 | 173 | Dave Perrett :: hello@daveperrett.com :: [@daveperrett](http://twitter.com/daveperrett) 174 | 175 | 176 | Copyright 177 | ========= 178 | 179 | Copyright (c) 2010 Dave Perrett. See [License](https://github.com/recurser/jekyll-plugins/blob/master/LICENSE) for details. 180 | 181 | -------------------------------------------------------------------------------- /generate_categories.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # 3 | # Jekyll category page generator. 4 | # http://recursive-design.com/projects/jekyll-plugins/ 5 | # 6 | # Version: 0.2.4 (201210160037) 7 | # 8 | # Copyright (c) 2010 Dave Perrett, http://recursive-design.com/ 9 | # Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php) 10 | # 11 | # A generator that creates category pages for jekyll sites. 12 | # 13 | # To use it, simply drop this script into the _plugins directory of your Jekyll site. You should 14 | # also create a file called 'category_index.html' in the _layouts directory of your jekyll site 15 | # with the following contents (note: you should remove the leading '# ' characters): 16 | # 17 | # ================================== COPY BELOW THIS LINE ================================== 18 | # --- 19 | # layout: default 20 | # --- 21 | # 22 | #

{{ page.title }}

23 | # 30 | # ================================== COPY ABOVE THIS LINE ================================== 31 | # 32 | # You can alter the _layout_ setting if you wish to use an alternate layout, and obviously you 33 | # can change the HTML above as you see fit. 34 | # 35 | # When you compile your jekyll site, this plugin will loop through the list of categories in your 36 | # site, and use the layout above to generate a page for each one with a list of links to the 37 | # individual posts. 38 | # 39 | # You can also (optionally) generate an atom.xml feed for each category. To do this, copy 40 | # the category_feed.xml file to the _includes/custom directory of your own project 41 | # (https://github.com/recurser/jekyll-plugins/blob/master/_includes/custom/category_feed.xml). 42 | # You'll also need to copy the octopress_filters.rb file into the _plugins directory of your 43 | # project as the category_feed.xml requires a couple of extra filters 44 | # (https://github.com/recurser/jekyll-plugins/blob/master/_plugins/octopress_filters.rb). 45 | # 46 | # Included filters : 47 | # - category_links: Outputs the list of categories as comma-separated links. 48 | # - date_to_html_string: Outputs the post.date as formatted html, with hooks for CSS styling. 49 | # 50 | # Available _config.yml settings : 51 | # - category_dir: The subfolder to build category pages in (default is 'categories'). 52 | # - category_title_prefix: The string used before the category name in the page title (default is 53 | # 'Category: '). 54 | module Jekyll 55 | 56 | # The CategoryIndex class creates a single category page for the specified category. 57 | class CategoryPage < Page 58 | 59 | # Initializes a new CategoryIndex. 60 | # 61 | # +template_path+ is the path to the layout template to use. 62 | # +site+ is the Jekyll Site instance. 63 | # +base+ is the String path to the . 64 | # +category_dir+ is the String path between and the category folder. 65 | # +category+ is the category currently being processed. 66 | def initialize(template_path, name, site, base, category_dir, category) 67 | @site = site 68 | @base = base 69 | @dir = category_dir 70 | @name = name 71 | 72 | self.process(name) 73 | 74 | if File.exist?(template_path) 75 | @perform_render = true 76 | template_dir = File.dirname(template_path) 77 | template = File.basename(template_path) 78 | # Read the YAML data from the layout page. 79 | self.read_yaml(template_dir, template) 80 | self.data['category'] = category 81 | # Set the title for this page. 82 | title_prefix = site.config['category_title_prefix'] || 'Category: ' 83 | self.data['title'] = "#{title_prefix}#{category}" 84 | # Set the meta-description for this page. 85 | meta_description_prefix = site.config['category_meta_description_prefix'] || 'Category: ' 86 | self.data['description'] = "#{meta_description_prefix}#{category}" 87 | else 88 | @perform_render = false 89 | end 90 | end 91 | 92 | def render? 93 | @perform_render 94 | end 95 | 96 | end 97 | 98 | # The CategoryIndex class creates a single category page for the specified category. 99 | class CategoryIndex < CategoryPage 100 | 101 | # Initializes a new CategoryIndex. 102 | # 103 | # +site+ is the Jekyll Site instance. 104 | # +base+ is the String path to the . 105 | # +category_dir+ is the String path between and the category folder. 106 | # +category+ is the category currently being processed. 107 | def initialize(site, base, category_dir, category) 108 | template_path = File.join(base, '_layouts', 'category_index.html') 109 | super(template_path, 'index.html', site, base, category_dir, category) 110 | end 111 | 112 | end 113 | 114 | # The CategoryFeed class creates an Atom feed for the specified category. 115 | class CategoryFeed < CategoryPage 116 | 117 | # Initializes a new CategoryFeed. 118 | # 119 | # +site+ is the Jekyll Site instance. 120 | # +base+ is the String path to the . 121 | # +category_dir+ is the String path between and the category folder. 122 | # +category+ is the category currently being processed. 123 | def initialize(site, base, category_dir, category) 124 | template_path = File.join(base, '_includes', 'custom', 'category_feed.xml') 125 | super(template_path, 'atom.xml', site, base, category_dir, category) 126 | 127 | # Set the correct feed URL. 128 | self.data['feed_url'] = "#{category_dir}/#{name}" if render? 129 | end 130 | 131 | end 132 | 133 | # The Site class is a built-in Jekyll class with access to global site config information. 134 | class Site 135 | 136 | # Creates an instance of CategoryIndex for each category page, renders it, and 137 | # writes the output to a file. 138 | # 139 | # +category+ is the category currently being processed. 140 | def write_category_index(category) 141 | target_dir = GenerateCategories.category_dir(self.config['category_dir'], category) 142 | index = CategoryIndex.new(self, self.source, target_dir, category) 143 | if index.render? 144 | index.render(self.layouts, site_payload) 145 | index.write(self.dest) 146 | # Record the fact that this pages has been added, otherwise Site::cleanup will remove it. 147 | self.pages << index 148 | end 149 | 150 | # Create an Atom-feed for each index. 151 | feed = CategoryFeed.new(self, self.source, target_dir, category) 152 | if feed.render? 153 | feed.render(self.layouts, site_payload) 154 | feed.write(self.dest) 155 | # Record the fact that this pages has been added, otherwise Site::cleanup will remove it. 156 | self.pages << feed 157 | end 158 | end 159 | 160 | # Loops through the list of category pages and processes each one. 161 | def write_category_indexes 162 | if self.layouts.key? 'category_index' 163 | self.categories.keys.each do |category| 164 | self.write_category_index(category) 165 | end 166 | 167 | # Throw an exception if the layout couldn't be found. 168 | else 169 | throw "No 'category_index' layout found." 170 | end 171 | end 172 | 173 | end 174 | 175 | 176 | # Jekyll hook - the generate method is called by jekyll, and generates all of the category pages. 177 | class GenerateCategories < Generator 178 | safe true 179 | priority :low 180 | 181 | CATEGORY_DIR = 'categories' 182 | 183 | def generate(site) 184 | site.write_category_indexes 185 | end 186 | 187 | # Processes the given dir and removes leading and trailing slashes. Falls 188 | # back on the default if no dir is provided. 189 | def self.category_dir(base_dir, category) 190 | base_dir = (base_dir || CATEGORY_DIR).gsub(/^\/*(.*)\/*$/, '\1') 191 | category = category.gsub(/_|\P{Word}/, '-').gsub(/-{2,}/, '-').downcase 192 | File.join(base_dir, category) 193 | end 194 | 195 | end 196 | 197 | 198 | # Adds some extra filters used during the category creation process. 199 | module Filters 200 | 201 | # Outputs a list of categories as comma-separated links. This is used 202 | # to output the category list for each post on a category page. 203 | # 204 | # +categories+ is the list of categories to format. 205 | # 206 | # Returns string 207 | def category_links(categories) 208 | base_dir = @context.registers[:site].config['category_dir'] 209 | categories = categories.sort!.map do |category| 210 | category_dir = GenerateCategories.category_dir(base_dir, category) 211 | # Make sure the category directory begins with a slash. 212 | category_dir = "/#{category_dir}" unless category_dir =~ /^\// 213 | "#{category}" 214 | end 215 | 216 | case categories.length 217 | when 0 218 | "" 219 | when 1 220 | categories[0].to_s 221 | else 222 | categories.join(', ') 223 | end 224 | end 225 | 226 | # Outputs the post.date as formatted html, with hooks for CSS styling. 227 | # 228 | # +date+ is the date object to format as HTML. 229 | # 230 | # Returns string 231 | def date_to_html_string(date) 232 | result = '' + date.strftime('%b').upcase + ' ' 233 | result += date.strftime('%d ') 234 | result += date.strftime('%Y ') 235 | result 236 | end 237 | 238 | end 239 | 240 | end -------------------------------------------------------------------------------- /generate_projects.rb: -------------------------------------------------------------------------------- 1 | # Jekyll project page generator. 2 | # http://recursive-design.com/projects/jekyll-plugins/ 3 | # 4 | # Version: 0.2.4 (201210160037) 5 | # 6 | # Copyright (c) 2010 Dave Perrett, http://recursive-design.com/ 7 | # Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php) 8 | # 9 | # Generator that creates project pages for jekyll sites from git repositories. 10 | # 11 | # This was inspired by the project pages on GitHub, which use the project README file as the index 12 | # page. It takes git repositories, and automatically builds project pages for them using the README 13 | # file, along with downloadable zipped copies of the projects themselves (for example, the project 14 | # page for this "plugin repository":http://recursive-design.com/projects/jekyll-plugins/ is 15 | # auto-generated with this plugin). 16 | # 17 | # To use it, simply drop this script into the _plugins directory of your Jekyll site. Next, create a 18 | # *_projects* folder in the base of your jekyll site. This folder should contain .yml files describing 19 | # how to build a page for your project. Here is an example jekyll-plugins.yml (note: you should remove 20 | # the leading '# ' characters): 21 | # 22 | # ================================== COPY BELOW THIS LINE ================================== 23 | # layout: default 24 | # title: Jekyll Plugins 25 | # repository: git://recursive-design.com/jekyll-plugins.git 26 | # published: true 27 | # ================================== COPY ABOVE THIS LINE ================================== 28 | # 29 | # There is also an optional 'zip_folder_name' setting, in case you want the unzipped folder to be named 30 | # something other than the project name. This is useful (for eaxmple) if you want it to unzip as an 31 | # OS X 'Something.app' application bundle. 32 | # 33 | # When you compile your jekyll site, the plugin will download the git repository of each project 34 | # in your _projects folder, create an index page from the README (using the specified layout), 35 | # and create a downloadable .zip file of the project. The goal is to automate the construction of 36 | # online project pages, keep them in sync with README documentation, and provide an up-to-date zip 37 | # archive for download. 38 | # 39 | # Required files : 40 | # Your project's git repository should contain: 41 | # - README: The contents of this will be used as the body of your project page will be created 42 | # from. Any extension other than .markdown, .textile or .html will be treated as a 43 | # .textile file. 44 | # - versions.txt: Contains the version string (eg 1.0.0). Used when naming the downloadable zip-file 45 | # (optional). If the version.txt file is not available, a YYYYMMDDHHMM timestamp will 46 | # be used for the version. 47 | # 48 | # Required gems : 49 | # - git (>= 1.2.5) 50 | # - rubyzip (>= 0.9.4) 51 | # 52 | # Available _config.yml settings : 53 | # - project_dir: The subfolder to compile projects to (default is 'projects'). 54 | # 55 | # Available YAML settings : 56 | # - repository: Git repository of your project (required). 57 | # - layout: Layout to use when creating the project page. 58 | # - title: Project title, which can be accessed in the layout. 59 | # - published: Project won't be published if this is false. 60 | 61 | require 'fileutils' 62 | require 'find' 63 | require 'git' 64 | require 'zip/zip' 65 | require 'zip/zipfilesystem' 66 | 67 | module Jekyll 68 | 69 | # The ProjectIndex class creates a single project page for the specified project. 70 | class ProjectIndex < Page 71 | 72 | # Initialize a new ProjectIndex. 73 | # +base_dir+ is the String path to the 74 | # +project_dir+ is the relative path from the base directory to the project folder. 75 | # +project_config_path+ is the String path to the project's yaml config file. 76 | # +project_name+ is the name of the project to process. 77 | def initialize(site, base_dir, project_dir, project_config_path, project_name) 78 | @site = site 79 | @base = base_dir 80 | @dir = project_dir 81 | 82 | self.data = load_config(base_dir, project_config_path) 83 | 84 | # Ignore the project unless it has been marked as published. 85 | unless self.data['published'] 86 | return false 87 | end 88 | 89 | # Clone the repo locally and get the path. 90 | zip_name = project_name 91 | if self.data['zip_folder_name'] 92 | zip_name = self.data['zip_folder_name'] 93 | end 94 | 95 | repo_dir = clone_repo(zip_name) 96 | 97 | # Get the version if possible. 98 | version = get_version(repo_dir) 99 | 100 | # Create the .zip file. 101 | self.data['download_link'] = create_zip(repo_dir, zip_name, project_dir, version) 102 | 103 | # Get the path to the README 104 | readme = get_readme_path(repo_dir) 105 | 106 | # Decide the extension - if it's not textile, markdown or HTML treat it as textile. 107 | ext = File.extname(readme) 108 | unless ['.textile', '.markdown', '.html'].include?(ext) 109 | ext = '.textile' 110 | end 111 | 112 | # Try to get the readme data for this path. 113 | self.content = File.read(readme) 114 | 115 | # Replace github-style '``` lang' code markup to pygments-compatible. 116 | self.content = self.content.gsub(/```([ ]?[a-z0-9]+)?(.*?)```/m, '{% highlight \1 %}\2{% endhighlight %}') 117 | 118 | @name = "index#{ext}" 119 | self.process(@name) 120 | end 121 | 122 | private 123 | 124 | # Loads the .yml config file for this project. 125 | # 126 | # +base_dir+ is the base path to the jekyll project. 127 | # +project_config_path+ is the String path to the project's yaml config file. 128 | # 129 | # Returns Array of project config information. 130 | def load_config(base_dir, project_config_path) 131 | yaml = File.read(File.join(base_dir, project_config_path)) 132 | YAML.load(yaml) 133 | end 134 | 135 | # Clones the project's repository to a temp folder. 136 | # 137 | # +project_name+ is the name of the project to process. 138 | # 139 | # Returns String path to the cloned repository. 140 | def clone_repo(zip_name) 141 | # Make the base clone directory if necessary. 142 | clone_dir = File.join(Dir.tmpdir(), 'checkout') 143 | unless File.directory?(clone_dir) 144 | p = Pathname.new(clone_dir) 145 | p.mkdir 146 | end 147 | 148 | # Remove any old repo at this location. 149 | repo_dir = File.join(clone_dir, zip_name) 150 | if File.directory?(repo_dir) 151 | FileUtils.remove_dir(repo_dir) 152 | end 153 | 154 | # Clone the repository. 155 | puts "Cloning #{self.data['repository']} to #{repo_dir}" 156 | Git.clone(self.data['repository'], zip_name, :path => clone_dir) 157 | repo_dir 158 | end 159 | 160 | # Gets the path to the README file for the project. 161 | # 162 | # +repo_dir+ is the path to the directory containing the checkout-out repository. 163 | # 164 | # Returns String path to the readme file. 165 | def get_readme_path(repo_dir) 166 | Find.find(repo_dir) do |file| 167 | if File.basename(file) =~ /^README(\.[a-z0-9\.]+)?$/i 168 | return file 169 | end 170 | end 171 | 172 | throw "No README file found in #{repo_dir}" 173 | end 174 | 175 | # Creates a zipped archive file of the downloaded repository. 176 | # 177 | # +repo_dir+ is the path to the directory containing the checkout-out repository. 178 | # +project_name+ is the name of the project to process. 179 | # +project_dir+ is the relative path from the base directory to the project folder. 180 | # +version+ is the version number to use when creating the zip file. 181 | # 182 | # Returns String path to the zip file. 183 | def create_zip(repo_dir, zip_name, project_dir, version) 184 | # Create the target folder if it doesn't exist. 185 | target_folder = File.join(@site.config['destination'], project_dir) 186 | unless File.directory?(target_folder) 187 | FileUtils.mkdir_p(target_folder) 188 | end 189 | 190 | # Decide the name of the bundle - use a timestamp if no version is available. 191 | unless version 192 | version = Time.now.strftime('%Y%m%d%H%M') 193 | end 194 | zip_filename = "#{zip_name}.#{version}.zip" 195 | bundle_filename = File.join(target_folder, zip_filename) 196 | puts "Creating #{bundle_filename}" 197 | 198 | # Remove the bundle if it already exists. 199 | if File.file?(bundle_filename) 200 | File.delete(bundle_filename) 201 | end 202 | 203 | Zip::ZipFile.open(bundle_filename, Zip::ZipFile::CREATE) do |zipfile| 204 | Find.find(repo_dir) do |path| 205 | # Remove .git files. 206 | Find.prune if File.basename(path) == '.git' 207 | # Trim the temp dir stuff off, leaving just the repo folder. 208 | parent = File.expand_path(File.dirname(repo_dir)) + '/' 209 | dest = path.sub parent, '' 210 | # Add the file to the bundle. 211 | zipfile.add(dest, path) if dest 212 | end 213 | 214 | # Add a static file entry for the zip file, otherwise Site::cleanup will remove it. 215 | @site.static_files << Jekyll::StaticProjectFile.new(@site, @site.dest, @dir, zip_filename) 216 | end 217 | 218 | # Set permissions. 219 | File.chmod(0644, bundle_filename) 220 | 221 | File.basename(bundle_filename) 222 | end 223 | 224 | # Get the version of the project from version.txt if possible. 225 | # 226 | # +repo_dir+ is the path to the directory containing the checkout-out repository. 227 | # 228 | # Returns String version number of the project if it exists, false otherwise. 229 | def get_version(repo_dir) 230 | Find.find(repo_dir) do |file| 231 | if File.basename(file) =~ /^VERSION(\.[a-z0-9]+)?/i 232 | # Remove *all* whitespace from the version, since we may be using it in a filename. 233 | return File.read(file).gsub(/\s+/, '') 234 | end 235 | end 236 | 237 | false 238 | end 239 | 240 | end 241 | 242 | 243 | # The Site class is a built-in Jekyll class with access to global site config information. 244 | class Site 245 | 246 | # Folder containing project .yml files. 247 | PROJECT_FOLDER = '_projects' 248 | 249 | # Loops through the list of project pages and processes each one. 250 | def write_project_indexes 251 | base_dir = self.config['project_dir'] || 'projects' 252 | projects = self.get_project_files 253 | projects.each do |project_config_path| 254 | project_name = project_config_path.sub(/^#{PROJECT_FOLDER}\/([^\.]+)\..*/, '\1') 255 | self.write_project_index(File.join(base_dir, project_name), project_config_path, project_name) 256 | end 257 | end 258 | 259 | # Writes each project page. 260 | # 261 | # +project_dir+ is the relative path from the base directory to the project folder. 262 | # +project_config_path+ is the String path to the project's yaml config file. 263 | # +project_name+ is the name of the project to process. 264 | def write_project_index(project_dir, project_config_path, project_name) 265 | index = ProjectIndex.new(self, self.source, project_dir, project_config_path, project_name) 266 | # Check that the project has been published. 267 | if index.data['published'] 268 | index.render(self.layouts, site_payload) 269 | index.write(self.dest) 270 | # Record the fact that this page has been added, otherwise Site::cleanup will remove it. 271 | self.static_files << Jekyll::StaticProjectFile.new(self, self.dest, project_dir, 'index.html') 272 | end 273 | end 274 | 275 | # Gets a list of files in the _projects folder with a .yml extension. 276 | # 277 | # Return Array list of project config files. 278 | def get_project_files 279 | projects = [] 280 | Find.find(PROJECT_FOLDER) do |file| 281 | if file=~/.yml$/ 282 | projects << file 283 | end 284 | end 285 | 286 | projects 287 | end 288 | 289 | end 290 | 291 | 292 | # Sub-class Jekyll::StaticFile to allow recovery from an unimportant exception when writing zip files. 293 | class StaticProjectFile < StaticFile 294 | def write(dest) 295 | super(dest) rescue ArgumentError 296 | true 297 | end 298 | end 299 | 300 | 301 | # Jekyll hook - the generate method is called by jekyll, and generates all the project pages. 302 | class GenerateProjects < Generator 303 | safe true 304 | priority :low 305 | 306 | def generate(site) 307 | site.write_project_indexes 308 | end 309 | 310 | end 311 | 312 | end --------------------------------------------------------------------------------