Merge branch 'master' into site

Conflicts:
	.themes/classic/source/javascripts/github.js
	Rakefile
	_config.yml
This commit is contained in:
Brandon Mathis 2012-01-08 17:29:25 -06:00
commit 34173ec3f5
49 changed files with 1040 additions and 143 deletions

2
.rvmrc
View File

@ -1 +1 @@
rvm 1.9.2 rvm use 1.9.2

View File

@ -14,6 +14,10 @@ $sidebar-width-wide: 300px !default;
$indented-lists: false !default; $indented-lists: false !default;
$header-font-size: 1em !default;
$header-padding-top: 1.5em !default;
$header-padding-bottom: 1.5em !default;
.group { @include pie-clearfix; } .group { @include pie-clearfix; }
@mixin collapse-sidebar { @mixin collapse-sidebar {
@ -32,7 +36,10 @@ $indented-lists: false !default;
&.thirds section { &.thirds section {
width: 30%; width: 30%;
margin-left: 5%; margin-left: 5%;
&.first { margin-left: 0; } &.first {
margin-left: 0;
clear: both;
}
} }
} }
@ -41,7 +48,7 @@ body {
max-width: $max-width; max-width: $max-width;
position: relative; position: relative;
margin: 0 auto; margin: 0 auto;
> header, > nav, > footer, #content > article, #content > div > article, #content > div > section, nav.pagination { > header, > nav, > footer, #content > article, #content > div > article, #content > div > section {
@extend .group; @extend .group;
padding-left: $pad-min; padding-left: $pad-min;
padding-right: $pad-min; padding-right: $pad-min;
@ -58,10 +65,27 @@ body {
padding-right: $pad-wide; padding-right: $pad-wide;
} }
} }
div.pagination {
@extend .group;
margin-left: $pad-min;
margin-right: $pad-min;
@media only screen and (min-width: 480px) {
margin-left: $pad-narrow;
margin-right: $pad-narrow;
}
@media only screen and (min-width: 768px) {
margin-left: $pad-medium;
margin-right: $pad-medium;
}
@media only screen and (min-width: 992px) {
margin-left: $pad-wide;
margin-right: $pad-wide;
}
}
> header { > header {
font-size: 1em; font-size: $header-font-size;
padding-top: 1.5em; padding-top: $header-padding-top;
padding-bottom: 1.5em; padding-bottom: $header-padding-bottom;
} }
} }
@ -95,14 +119,14 @@ body.sidebar-footer {
} }
@media only screen and (min-width: 550px) { @media only screen and (min-width: 550px) {
body > header { font-size: 1em; } body > header { font-size: $header-font-size; }
} }
@media only screen and (min-width: 750px) { @media only screen and (min-width: 750px) {
aside.sidebar { @include collapse-sidebar; } aside.sidebar { @include collapse-sidebar; }
} }
@media only screen and (min-width: 768px) { @media only screen and (min-width: 768px) {
body { -webkit-text-size-adjust: auto; } body { -webkit-text-size-adjust: auto; }
body > header { font-size: 1.2em; } body > header { font-size: $header-font-size * 1.2; }
#main { #main {
@extend .group; @extend .group;
padding: 0; padding: 0;
@ -139,7 +163,7 @@ body.sidebar-footer {
} }
@media only screen and (min-width: 992px) { @media only screen and (min-width: 992px) {
body > header { font-size: 1.3em; } body > header { font-size: $header-font-size * 1.3; }
#content { margin-right: $sidebar-width-wide; } #content { margin-right: $sidebar-width-wide; }
#content { #content {
> div, > article { > div, > article {

View File

@ -1,4 +1,4 @@
$noise-bg: image-url('noise.png') top left; $noise-bg: image-url('noise.png') top left !default;
$img-border: inline-image('dotted-border.png'); $img-border: inline-image('dotted-border.png');
// Main Link Colors // Main Link Colors
@ -23,6 +23,8 @@ $type-border: #ddd !default;
/* Navigation */ /* Navigation */
$nav-bg: #ccc !default; $nav-bg: #ccc !default;
$nav-bg-front: image-url('noise.png') !default;
$nav-bg-back: linear-gradient(lighten($nav-bg, 8), $nav-bg, darken($nav-bg, 11)) !default;
$nav-color: darken($nav-bg, 38) !default; $nav-color: darken($nav-bg, 38) !default;
$nav-color-hover: darken($nav-color, 25) !default; $nav-color-hover: darken($nav-color, 25) !default;
$nav-placeholder: desaturate(darken($nav-bg, 10), 15) !default; $nav-placeholder: desaturate(darken($nav-bg, 10), 15) !default;
@ -39,13 +41,15 @@ $sidebar-link-color-hover: $link-color-hover !default;
$sidebar-link-color-active: $link-color-active !default; $sidebar-link-color-active: $link-color-active !default;
$sidebar-color: change-color(mix($text-color, $sidebar-bg, 80), $hue: hue($sidebar-bg), $saturation: saturation($sidebar-bg)/2) !default; $sidebar-color: change-color(mix($text-color, $sidebar-bg, 80), $hue: hue($sidebar-bg), $saturation: saturation($sidebar-bg)/2) !default;
$sidebar-border: desaturate(darken($sidebar-bg, 7), 10) !default; $sidebar-border: desaturate(darken($sidebar-bg, 7), 10) !default;
$sidebar-border: darken($sidebar-bg, 7) !default; $sidebar-border-hover: darken($sidebar-bg, 7) !default;
$sidebar-link-color-subdued: lighten($sidebar-color, 20) !default; $sidebar-link-color-subdued: lighten($sidebar-color, 20) !default;
$sidebar-link-color-subdued-hover: $sidebar-link-color-hover !default; $sidebar-link-color-subdued-hover: $sidebar-link-color-hover !default;
$twitter-status-link: lighten($sidebar-link-color-subdued, 15) !default; $twitter-status-link: lighten($sidebar-link-color-subdued, 15) !default;
$footer-color: #888 !default; $footer-color: #888 !default;
$footer-bg: #ccc !default; $footer-bg: #ccc !default;
$footer-bg-front: image-url('noise.png') !default;
$footer-bg-back: linear-gradient(lighten($footer-bg, 8), $footer-bg, darken($footer-bg, 11)) !default;
$footer-color: darken($footer-bg, 38) !default; $footer-color: darken($footer-bg, 38) !default;
$footer-color-hover: darken($footer-color, 10) !default; $footer-color-hover: darken($footer-color, 10) !default;
$footer-border-top: lighten($footer-bg, 15) !default; $footer-border-top: lighten($footer-bg, 15) !default;

View File

@ -1,21 +1,30 @@
$blockquote: $type-border !default; $blockquote: $type-border !default;
$mono: Menlo, Monaco, "Andale Mono", "lucida console", "Courier New", monospace; $sans: "PT Sans", "Helvetica Neue", Arial, sans-serif !default;
$serif: "PT Serif", Georgia, Times, "Times New Roman", serif !default;
$mono: Menlo, Monaco, "Andale Mono", "lucida console", "Courier New", monospace !default;
$heading-font-family: "PT Serif", "Georgia", "Helvetica Neue", Arial, sans-serif !default;
$header-title-font-family: $heading-font-family !default;
$header-subtitle-font-family: $heading-font-family !default;
// Fonts // Fonts
.heading { .heading {
font-family: "PT Serif", "Georgia", "Helvetica Neue", Arial, sans-serif; font-family: $heading-font-family;
} }
.sans { font-family: "PT Sans", "Helvetica Neue", Arial, sans-serif; } .sans { font-family: $sans; }
.serif { font-family: "PT Serif", Georgia, Times, "Times New Roman", serif; } .serif { font-family: $serif; }
.mono { font-family: $mono; } .mono { font-family: $mono; }
body > header h1 { body > header h1 {
font-size: 2.2em; font-size: 2.2em;
@extend .heading; @extend .heading;
font-family: $header-title-font-family;
font-weight: normal; font-weight: normal;
line-height: 1.2em; line-height: 1.2em;
margin-bottom: 0.6667em; margin-bottom: 0.6667em;
} }
body > header h2 {
font-family: $header-subtitle-font-family;
}
body { body {
line-height: 1.5em; line-height: 1.5em;
@ -56,12 +65,12 @@ h6, section h5, section section h4, section section section h3 {
p, blockquote, ul, ol { margin-bottom: 1.5em; } p, blockquote, ul, ol { margin-bottom: 1.5em; }
ul { list-style-type: disc; ul { list-style-type: disc;
ul { list-style-type: circle; ul { list-style-type: circle; margin-bottom: 0px;
ul { list-style-type: square; }}} ul { list-style-type: square; margin-bottom: 0px; }}}
ol { list-style-type: decimal; ol { list-style-type: decimal;
ol { list-style-type: lower-alpha; ol { list-style-type: lower-alpha; margin-bottom: 0px;
ol { list-style-type: lower-roman; }}} ol { list-style-type: lower-roman; margin-bottom: 0px; }}}
ul, ol { &, ul, ol { margin-left: 1.3em; }} ul, ol { &, ul, ol { margin-left: 1.3em; }}
@ -115,7 +124,8 @@ blockquote {
} }
} }
.has-pullquote:before { .pullquote-right:before,
.pullquote-left:before {
/* Reset metrics. */ /* Reset metrics. */
padding: 0; padding: 0;
border: none; border: none;
@ -134,6 +144,13 @@ blockquote {
font-size: 1.4em; font-size: 1.4em;
line-height: 1.45em; line-height: 1.45em;
} }
.pullquote-left:before {
/* Make left pullquotes align properly. */
float: left;
margin: .5em 1.5em 1em 0;
}
/* @extend this to force long lines of continuous text to wrap */ /* @extend this to force long lines of continuous text to wrap */
.force-wrap { .force-wrap {
white-space: -moz-pre-wrap; white-space: -moz-pre-wrap;

View File

@ -5,9 +5,14 @@
//$header-bg: #263347; //$header-bg: #263347;
//$subtitle-color: lighten($header-bg, 58); //$subtitle-color: lighten($header-bg, 58);
//$nav-bg: desaturate(lighten(#8fc17a, 18), 5); //$nav-bg: desaturate(lighten(#8fc17a, 18), 5);
//$nav-bg-front: image-url('noise.png');
//$nav-bg-back: linear-gradient(lighten($nav-bg, 8), $nav-bg, darken($nav-bg, 11));
//$sidebar-bg: desaturate(#eceff5, 8); //$sidebar-bg: desaturate(#eceff5, 8);
//$sidebar-link-color: saturate(#526f9a, 10); //$sidebar-link-color: saturate(#526f9a, 10);
//$sidebar-link-color-hover: darken(#7ab662, 9); //$sidebar-link-color-hover: darken(#7ab662, 9);
//$footer-bg: #ccc !default;
//$footer-bg-front: image-url('noise.png');
//$footer-bg-back: linear-gradient(lighten($footer-bg, 8), $footer-bg, darken($footer-bg, 11));
/* To use the light Solarized highlighting theme uncomment the following line */ /* To use the light Solarized highlighting theme uncomment the following line */

View File

@ -0,0 +1,10 @@
// Here you can easily change font faces which are used in your site.
// To give it a try, uncomment some of the lines below rebuild your blog, and see how it works. your sites's.
// If you love to use Web Fonts, you also need to add some lines to source/_includes/custom/head.html
//$sans: "Optima", sans-serif;
//$serif: "Baskerville", serif;
//$mono: "Courier", monospace;
//$heading-font-family: "Verdana", sans-serif;
//$header-title-font-family: "Futura", sans-serif;
//$header-subtitle-font-family: "Futura", sans-serif;

View File

@ -1,6 +1,10 @@
// Here you can easily change your sites's layout. // Here you can easily change your sites's layout.
// To give it a try, uncomment some of the lines below, make changes, rebuild your blog, and see how it works. // To give it a try, uncomment some of the lines below, make changes, rebuild your blog, and see how it works.
//$header-font-size: 1em;
//$header-padding-top: 1.5em;
//$header-padding-bottom: 1.5em;
//$max-width: 1350px; //$max-width: 1350px;
//$indented-lists: true; //$indented-lists: true;

View File

@ -1,5 +1,4 @@
article { article {
overflow: hidden;
padding-top: 1em; padding-top: 1em;
a { @extend .force-wrap; } a { @extend .force-wrap; }
header { header {
@ -74,6 +73,8 @@ article {
p.meta { p.meta {
margin-bottom: .8em; margin-bottom: .8em;
font-size: .85em; font-size: .85em;
clear: both;
overflow: hidden;
} }
.byline + time:before, time +time:before, .comments:before, .byline ~ .categories:before { .byline + time:before, time +time:before, .comments:before, .byline ~ .categories:before {
@extend .separator; @extend .separator;
@ -121,14 +122,12 @@ article + article {
display: inline-block; display: inline-block;
} }
#content nav.pagination { #content div.pagination {
text-align: center; text-align: center;
font-size: .95em; font-size: .95em;
div { position: relative;
position: relative; background: $img-border top left repeat-x;
background: $img-border top left repeat-x; padding: {top: 1.5em; bottom: 1.5em;}
padding: {top: 1.5em; bottom: 1.5em;}
}
a { a {
text-decoration: none; text-decoration: none;
color: $text-color-light; color: $text-color-light;

View File

@ -4,7 +4,7 @@ body > footer {
color: $footer-color; color: $footer-color;
text-shadow: lighten($footer-bg, 5) 0 1px; text-shadow: lighten($footer-bg, 5) 0 1px;
background-color: $footer-bg; background-color: $footer-bg;
@include background(image-url('noise.png'), linear-gradient(lighten($footer-bg, 8), $footer-bg, darken($footer-bg, 11))); @include background($footer-bg-front, $footer-bg-back);
border-top: 1px solid $footer-border-top; border-top: 1px solid $footer-border-top;
position: relative; position: relative;
padding-top: 1em; padding-top: 1em;

View File

@ -1,7 +1,7 @@
body > nav { body > nav {
position: relative; position: relative;
background-color: $nav-bg; background-color: $nav-bg;
@include background(image-url('noise.png'), linear-gradient(lighten($nav-bg, 8), $nav-bg, darken($nav-bg, 11))); @include background($nav-bg-front, $nav-bg-back);
border: { border: {
top: 1px solid $nav-border-top; top: 1px solid $nav-border-top;
bottom: 1px solid $nav-border-bottom; } bottom: 1px solid $nav-border-bottom; }

View File

@ -1,4 +1,5 @@
@import "sidebar/base"; @import "sidebar/base";
@import "sidebar/twitter"; @import "sidebar/twitter";
@import "sidebar/googleplus";
@import "sidebar/pinboard"; @import "sidebar/pinboard";
@import "sidebar/delicious"; @import "sidebar/delicious";

View File

@ -0,0 +1,26 @@
.googleplus {
h1 {
-moz-box-shadow: none !important;
-webkit-box-shadow: none !important;
-o-box-shadow: none !important;
box-shadow: none !important;
border-bottom: 0px none !important;
}
a {
text-decoration: none;
white-space: normal !important;
line-height: 32px;
img {
float: left;
margin-right: 0.5em;
border: 0 none;
}
}
}
.googleplus-hidden {
position: absolute;
top: -1000em;
left: -1000em;
}

View File

@ -13,7 +13,7 @@
position: relative; position: relative;
padding-right: 1em; padding-right: 1em;
} }
a[href*=status]{ a[href*=status]:first-child {
color: $twitter-status-link; color: $twitter-status-link;
float: right; float: right;
padding: 0 0 .1em 1em; padding: 0 0 .1em 1em;

View File

@ -3,6 +3,7 @@
@include reset-html5; @include reset-html5;
@import "custom/colors"; @import "custom/colors";
@import "custom/fonts";
@import "custom/layout"; @import "custom/layout";
@import "base"; @import "base";
@import "partials"; @import "partials";

View File

@ -1,5 +1,5 @@
{% include disqus.html %} {% include disqus.html %}
{% include facebook_like.html %}
{% include google_plus_one.html %} {% include google_plus_one.html %}
{% include twitter_sharing.html %} {% include twitter_sharing.html %}
{% include google_analytics.html %}
{% include custom/after_footer.html %} {% include custom/after_footer.html %}

View File

@ -1,9 +1,9 @@
{% unless page.no_header %} {% unless page.no_header %}
<header> <header>
{% if index %} {% if index %}
<h1 class="entry-title"><a href="{{ root_url }}{{ post.url }}">{{ post.title | titlecase }}</a></h1> <h1 class="entry-title"><a href="{{ root_url }}{{ post.url }}">{% if site.titlecase %}{{ post.title | titlecase }}{% else %}{{ post.title }}{% endif %}</a></h1>
{% else %} {% else %}
<h1 class="entry-title">{{ page.title | titlecase }}</h1> <h1 class="entry-title">{% if site.titlecase %}{{ page.title | titlecase }}{% else %}{{ page.title }}{% endif %}</h1>
{% endif %} {% endif %}
{% unless page.meta == false %} {% unless page.meta == false %}
<p class="meta"> <p class="meta">

View File

@ -2,7 +2,7 @@
<section> <section>
<h1>On Delicious</h1> <h1>On Delicious</h1>
<div id="delicious"></div> <div id="delicious"></div>
<script type="text/javascript" src="http://feeds.delicious.com/v1/json/{{ site.delicious_user }}?count={{ site.delicious_count }}&amp;sort=date&amp;callback=renderDeliciousLinks"></script> <script type="text/javascript" src="http://feeds.delicious.com/v2/json/{{ site.delicious_user }}?count={{ site.delicious_count }}&amp;sort=date&amp;callback=renderDeliciousLinks"></script>
<p><a href="http://delicious.com/{{ site.delicious_user }}">My Delicious Bookmarks &raquo;</a></p> <p><a href="http://delicious.com/{{ site.delicious_user }}">My Delicious Bookmarks &raquo;</a></p>
</section> </section>
{% endif %} {% endif %}

View File

@ -0,0 +1,11 @@
{% if site.googleplus_user %}
<section class="googleplus{% if site.googleplus_hidden %} googleplus-hidden{% endif %}">
<h1>
<a href="https://plus.google.com/{{ site.googleplus_user }}?rel=author">
<img src="http://www.google.com/images/icons/ui/gprofile_button-32.png" width="32" height="32">
Google+
</a>
</h1>
</section>
{% endif %}

View File

@ -11,7 +11,7 @@
</script> </script>
<script src="{{ root_url }}/javascripts/twitter.js" type="text/javascript"> </script> <script src="{{ root_url }}/javascripts/twitter.js" type="text/javascript"> </script>
{% if site.twitter_follow_button %} {% if site.twitter_follow_button %}
<a href="http://twitter.com/{{ site.twitter_user }}" class="twitter-follow-button" data-width="208px" data-show-count="{{ site.twitter_show_follower_count }}">Follow @{{ site.twitter_user }}</a> <a href="http://twitter.com/{{ site.twitter_user }}" class="twitter-follow-button" data-show-count="{{ site.twitter_show_follower_count }}">Follow @{{ site.twitter_user }}</a>
{% else %} {% else %}
<p>Follow <a href="http://twitter.com/{{site.twitter_user}}">@{{ site.twitter_user }}</a></p> <p>Follow <a href="http://twitter.com/{{site.twitter_user}}">@{{ site.twitter_user }}</a></p>
{% endif %} {% endif %}

View File

@ -0,0 +1,27 @@
---
layout: nil
---
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title><![CDATA[{{ page.title }} | {{ site.title }}]]></title>
<link href="{{ site.url }}/{{ page.feed_url }}" rel="self"/>
<link href="{{ site.url }}/"/>
<updated>{{ site.time | date_to_xmlschema }}</updated>
<id>{{ site.url }}/</id>
<author>
<name><![CDATA[{{ site.author | strip_html }}]]></name>
{% if site.email %}<email><![CDATA[{{ site.email }}]]></email>{% endif %}
</author>
<generator uri="http://octopress.org/">Octopress</generator>
{% for post in site.categories[page.category] limit: 5 %}
<entry>
<title type="html"><![CDATA[{{ post.title | cdata_escape }}]]></title>
<link href="{{ site.url }}{{ post.url }}"/>
<updated>{{ post.date | date_to_xmlschema }}</updated>
<id>{{ site.url }}{{ post.id }}</id>
<content type="html"><![CDATA[{{ post.content | expand_urls: site.url | markdownify | cdata_escape }}]]></content>
</entry>
{% endfor %}
</feed>

View File

@ -1,19 +1,18 @@
{% comment %} Load script if disquss comments are enabled and `page.comments` is either empty (index) or set to true {% endcomment %} {% comment %} Load script if disquss comments are enabled and `page.comments` is either empty (index) or set to true {% endcomment %}
{% if site.disqus_short_name and page.comments != false %} {% if site.disqus_short_name and page.comments != false %}
<script type="text/javascript"> <script type="text/javascript">
(function () {
var disqus_shortname = '{{ site.disqus_short_name }}'; var disqus_shortname = '{{ site.disqus_short_name }}';
{% if page.comments == true %} {% if page.comments == true %}
{% comment %} `page.comments` can be only be set to true on pages/posts, so we embed the comments here. {% endcomment %} {% comment %} `page.comments` can be only be set to true on pages/posts, so we embed the comments here. {% endcomment %}
// var disqus_developer = 1; // var disqus_developer = 1;
var disqus_identifier = '{{ site.url }}{{ page.url }}'; var disqus_identifier = '{{ site.url }}{{ page.url }}';
var disqus_url = '{{ site.url }}{{ page.url }}'; var disqus_url = '{{ site.url }}{{ page.url }}';
var disqus_script = 'embed.js' var disqus_script = 'embed.js';
{% else %} {% else %}
{% comment %} As `page.comments` is empty, we must be on the index page. {% endcomment %} {% comment %} As `page.comments` is empty, we must be on the index page. {% endcomment %}
var disqus_script = 'count.js' var disqus_script = 'count.js';
{% endif %} {% endif %}
(function () {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = 'http://' + disqus_shortname + '.disqus.com/' + disqus_script; dsq.src = 'http://' + disqus_shortname + '.disqus.com/' + disqus_script;
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);

View File

@ -0,0 +1,10 @@
{% if site.facebook_like %}
<div id="fb-root"></div>
<script>(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {return;}
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/all.js#appId=212934732101925&xfbml=1";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
{% endif %}

View File

@ -8,7 +8,7 @@
<meta name="author" content="{{ site.author }}"> <meta name="author" content="{{ site.author }}">
{% capture description %}{% if page.description %}{{ page.description }}{% else %}{{ content | raw_content }}{% endif %}{% endcapture %} {% capture description %}{% if page.description %}{{ page.description }}{% else %}{{ content | raw_content }}{% endif %}{% endcapture %}
<meta name="description" content="{{ description | strip_newlines | strip_html | truncate:150 }}"> <meta name="description" content="{{ description | strip_html | condense_spaces | truncate:150 }}">
{% if page.keywords %}<meta name="keywords" content="{{ page.keywords }}">{% endif %} {% if page.keywords %}<meta name="keywords" content="{{ page.keywords }}">{% endif %}
<!-- http://t.co/dKP3o1e --> <!-- http://t.co/dKP3o1e -->
@ -16,7 +16,7 @@
<meta name="MobileOptimized" content="320"> <meta name="MobileOptimized" content="320">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
{% capture canonical %}{{ site.url }}{% if site.permalink contains '.html' %}{{ page.url }}{% else %}{{ page.url | remove:'index.html' }}{% endif %}{% endcapture %} {% capture canonical %}{{ site.url }}{% if site.permalink contains '.html' %}{{ page.url }}{% else %}{{ page.url | remove:'index.html' | strip_slash }}{% endif %}{% endcapture %}
<link rel="canonical" href="{{ canonical }}"> <link rel="canonical" href="{{ canonical }}">
<link href="{{ root_url }}/favicon.png" rel="icon"> <link href="{{ root_url }}/favicon.png" rel="icon">
<link href="{{ root_url }}/stylesheets/screen.css" media="screen, projection" rel="stylesheet" type="text/css"> <link href="{{ root_url }}/stylesheets/screen.css" media="screen, projection" rel="stylesheet" type="text/css">
@ -25,4 +25,5 @@
<script src="{{ root_url }}/javascripts/octopress.js" type="text/javascript"></script> <script src="{{ root_url }}/javascripts/octopress.js" type="text/javascript"></script>
<link href="{{ site.subscribe_rss }}" rel="alternate" title="{{site.title}}" type="application/atom+xml"> <link href="{{ site.subscribe_rss }}" rel="alternate" title="{{site.title}}" type="application/atom+xml">
{% include custom/head.html %} {% include custom/head.html %}
{% include google_analytics.html %}
</head> </head>

View File

@ -4,10 +4,12 @@
<li><a href="{{ site.subscribe_email }}" rel="subscribe-email" title="subscribe via email">Email</a></li> <li><a href="{{ site.subscribe_email }}" rel="subscribe-email" title="subscribe via email">Email</a></li>
{% endif %} {% endif %}
</ul> </ul>
{% if site.simple_search %}
<form action="{{ site.simple_search }}" method="get"> <form action="{{ site.simple_search }}" method="get">
<fieldset role="search"> <fieldset role="search">
<input type="hidden" name="q" value="site:{{ site.url | shorthand_url }}" /> <input type="hidden" name="q" value="site:{{ site.url | shorthand_url }}" />
<input class="search" type="text" name="q" results="0" placeholder="Search"/> <input class="search" type="text" name="q" results="0" placeholder="Search"/>
</fieldset> </fieldset>
</form> </form>
{% endif %}
{% include custom/navigation.html %} {% include custom/navigation.html %}

View File

@ -1,12 +1,15 @@
{% capture date %}{{ page.date }}{{ post.date }}{% endcapture %} {% capture date %}{{ page.date }}{{ post.date }}{% endcapture %}
{% capture date_formatted %}{{ page.date_formatted }}{{ post.date_formatted }}{% endcapture %}
{% capture has_date %}{{ date | size }}{% endcapture %} {% capture has_date %}{{ date | size }}{% endcapture %}
{% capture updated %}{{ page.updated }}{{ post.updated }}{% endcapture %} {% capture updated %}{{ page.updated }}{{ post.updated }}{% endcapture %}
{% capture updated_formatted %}{{ page.updated_formatted }}{{ post.updated_formatted }}{% endcapture %}
{% capture was_updated %}{{ updated | size }}{% endcapture %} {% capture was_updated %}{{ updated | size }}{% endcapture %}
{% if has_date != '0' %} {% if has_date != '0' %}
{% capture time %}<time datetime="{{ date | datetime | date_to_xmlschema }}" pubdate {% if updated %} data-updated="true" {% endif %}>{{ date | ordinalize }}</time>{% endcapture %} {% capture time %}<time datetime="{{ date | datetime | date_to_xmlschema }}" pubdate{% if updated %} data-updated="true"{% endif %}>{{ date_formatted }}</time>{% endcapture %}
{% endif %} {% endif %}
{% if was_updated != '0' %} {% if was_updated != '0' %}
{% capture updated %}<time class="updated" datetime="{{ updated | datetime | date_to_xmlschema }}">Updated {{ updated | ordinalize }}</time>{% endcapture %} {% capture updated %}<time datetime="{{ updated | datetime | date_to_xmlschema }}" class="updated">Updated {{ updated_formatted }}</time>{% endcapture %}
{% else %}{% assign updated = false %}{% endif %} {% else %}{% assign updated = false %}{% endif %}

View File

@ -5,4 +5,7 @@
{% if site.google_plus_one %} {% if site.google_plus_one %}
<div class="g-plusone" data-size="{{ site.google_plus_one_size }}"></div> <div class="g-plusone" data-size="{{ site.google_plus_one_size }}"></div>
{% endif %} {% endif %}
{% if site.facebook_like %}
<div class="fb-like" data-send="true" data-width="450" data-show-faces="false"></div>
{% endif %}
</div> </div>

View File

@ -6,7 +6,7 @@ layout: default
<article role="article"> <article role="article">
{% if page.title %} {% if page.title %}
<header> <header>
<h1 class="entry-title">{{ page.title | titlecase }}</h1> <h1 class="entry-title">{% if site.titlecase %}{{ page.title | titlecase }}{% else %}{{ page.title }}{% endif %}</h1>
{% if page.date %}<p class="meta">{% include post/date.html %}{{ time }}</p>{% endif %} {% if page.date %}<p class="meta">{% include post/date.html %}{{ time }}</p>{% endif %}
</header> </header>
{% endif %} {% endif %}

View File

@ -15,6 +15,14 @@ single: true
{% unless page.sharing == false %} {% unless page.sharing == false %}
{% include post/sharing.html %} {% include post/sharing.html %}
{% endunless %} {% endunless %}
<p class="meta">
{% if page.previous.url %}
<a class="basic-alignment left" href="{{page.previous.url}}" title="Previous Post: {{page.previous.title}}">&laquo; {{page.previous.title}}</a>
{% endif %}
{% if page.next.url %}
<a class="basic-alignment right" href="{{page.next.url}}" title="next Post: {{page.next.title}}">{{page.next.title}} &raquo;</a>
{% endif %}
</p>
</footer> </footer>
</article> </article>
{% if site.disqus_short_name and page.comments == true %} {% if site.disqus_short_name and page.comments == true %}

View File

@ -4,25 +4,24 @@ layout: nil
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"> <feed xmlns="http://www.w3.org/2005/Atom">
<title>{{ site.title }}</title> <title><![CDATA[{{ site.title }}]]></title>
<link href="{{ site.url }}/atom.xml" rel="self"/> <link href="{{ site.url }}/atom.xml" rel="self"/>
<link href="{{ site.url }}/"/> <link href="{{ site.url }}/"/>
<updated>{{ site.time | date_to_xmlschema }}</updated> <updated>{{ site.time | date_to_xmlschema }}</updated>
<id>{{ site.url }}/</id> <id>{{ site.url }}/</id>
<author> <author>
<name>{{ site.author }}</name> <name><![CDATA[{{ site.author | strip_html }}]]></name>
{% if site.email %} {% if site.email %}<email><![CDATA[{{ site.email }}]]></email>{% endif %}
<email>{{ site.email }}</email>
{% endif %}
</author> </author>
<generator uri="http://octopress.org/">Octopress</generator>
{% for post in site.posts limit: 20 %} {% for post in site.posts limit: 20 %}
<entry> <entry>
<title>{{ post.title }}</title> <title type="html"><![CDATA[{{ post.title | cdata_escape }}]]></title>
<link href="{{ site.url }}{{ post.url }}"/> <link href="{{ site.url }}{{ post.url }}"/>
<updated>{{ post.date | date_to_xmlschema }}</updated> <updated>{{ post.date | date_to_xmlschema }}</updated>
<id>{{ site.url }}{{ post.id }}</id> <id>{{ site.url }}{{ post.id }}</id>
<content type="html">{{ post.content | expand_urls: site.url | xml_escape }}</content> <content type="html"><![CDATA[{{ post.content | expand_urls: site.url | cdata_escape }}]]></content>
</entry> </entry>
{% endfor %} {% endfor %}
</feed> </feed>

View File

@ -10,17 +10,15 @@ layout: default
{% include article.html %} {% include article.html %}
</article> </article>
{% endfor %} {% endfor %}
<nav class="pagination"> <div class="pagination">
<div> {% if paginator.next_page %}
{% if paginator.next_page %} <a class="prev" href="{{paginator.next_page}}">&larr; Older</a>
<a class="prev" href="{{paginator.next_page}}">&larr; Older</a> {% endif %}
{% endif %} <a href="/blog/archives">Blog Archives</a>
<a href="/blog/archives">Blog Archives</a> {% if paginator.previous_page %}
{% if paginator.previous_page %} <a class="next" href="{{paginator.previous_page}}">Newer &rarr;</a>
<a class="next" href="{{paginator.previous_page}}">Newer &rarr;</a> {% endif %}
{% endif %} </div>
</div>
</nav>
</div> </div>
<aside class="sidebar"> <aside class="sidebar">
{% if site.blog_index_asides.size %} {% if site.blog_index_asides.size %}

View File

@ -15,7 +15,8 @@ var github = (function(){
, error: function (err) { $(options.target + ' li.loading').addClass('error').text("Error loading feed"); } , error: function (err) { $(options.target + ' li.loading').addClass('error').text("Error loading feed"); }
, success: function(data) { , success: function(data) {
var repos = []; var repos = [];
for (var i = 0; i < data.repositories.length; i++){ if (!data || !data.repositories) { return; }
for (var i = 0; i < data.repositories.length; i++) {
if (options.skip_forks && data.repositories[i].fork) { continue; } if (options.skip_forks && data.repositories[i].fork) { continue; }
repos.push(data.repositories[i]); repos.push(data.repositories[i]);
} }
@ -30,7 +31,7 @@ var github = (function(){
if (options.count) { repos.splice(options.count); } if (options.count) { repos.splice(options.count); }
render(options.target, repos); render(options.target, repos);
} }
}) });
} }
}; };
})(); })();

View File

@ -3,7 +3,10 @@ function getNav() {
mobileNav.children('select').append('<option value="">Navigate&hellip;</option>'); mobileNav.children('select').append('<option value="">Navigate&hellip;</option>');
$('ul[role=main-navigation]').addClass('main-navigation'); $('ul[role=main-navigation]').addClass('main-navigation');
$('ul.main-navigation a').each(function(link) { $('ul.main-navigation a').each(function(link) {
mobileNav.children('select').append('<option value="'+link.href+'">&bull; '+link.text+'</option>'); mobileNav.children('select').append('<option value="'+link.href+'">&raquo; '+link.text+'</option>');
});
$('ul.subscription a').each(function(link) {
mobileNav.children('select').append('<option value="'+link.href+'">&raquo; '+link.text+'</option>');
}); });
mobileNav.children('select').bind('change', function(event) { mobileNav.children('select').bind('change', function(event) {
if (event.target.value) { window.location.href = event.target.value; } if (event.target.value) { window.location.href = event.target.value; }
@ -107,7 +110,7 @@ function wrapFlashVideos() {
function renderDeliciousLinks(items) { function renderDeliciousLinks(items) {
var output = "<ul>"; var output = "<ul>";
for (var i=0,l=items.length; i<l; i++) { for (var i=0,l=items.length; i<l; i++) {
output += '<li><a href="' + items[i].u + '" title="Tags: ' + items[i].t.join(', ') + '">' + items[i].d + '</a></li>'; output += '<li><a href="' + items[i].u + '" title="Tags: ' + (items[i].t == "" ? "" : items[i].t.join(', ')) + '">' + items[i].d + '</a></li>';
} }
output += "</ul>"; output += "</ul>";
$('#delicious').html(output); $('#delicious').html(output);
@ -155,3 +158,4 @@ b=j.userAgent.toLowerCase(),d=j.platform.toLowerCase(),g=d?/win/.test(d):/win/.t
10),e[1]=parseInt(c.replace(/^.*\.(.*)\s.*$/,"$1"),10),e[2]=/[a-zA-Z]/.test(c)?parseInt(c.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}else if(typeof v.ActiveXObject!=l)try{var f=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");if(f&&(c=f.GetVariable("$version")))k=!0,c=c.split(" ")[1].split(","),e=[parseInt(c[0],10),parseInt(c[1],10),parseInt(c[2],10)]}catch(h){}return{w3:a,pv:e,wk:b,ie:k,win:g,mac:d}}();return{embedSWF:function(a,b,d,i,k,e,c,f,h){var j={success:!1,id:b};if(g.w3&&!(g.wk&&g.wk<312)&& 10),e[1]=parseInt(c.replace(/^.*\.(.*)\s.*$/,"$1"),10),e[2]=/[a-zA-Z]/.test(c)?parseInt(c.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}else if(typeof v.ActiveXObject!=l)try{var f=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");if(f&&(c=f.GetVariable("$version")))k=!0,c=c.split(" ")[1].split(","),e=[parseInt(c[0],10),parseInt(c[1],10),parseInt(c[2],10)]}catch(h){}return{w3:a,pv:e,wk:b,ie:k,win:g,mac:d}}();return{embedSWF:function(a,b,d,i,k,e,c,f,h){var j={success:!1,id:b};if(g.w3&&!(g.wk&&g.wk<312)&&
a&&b&&d&&i&&k){d+="";i+="";var p={};if(f&&typeof f===o)for(var m in f)p[m]=f[m];p.data=a;p.width=d;p.height=i;a={};if(c&&typeof c===o)for(var n in c)a[n]=c[n];if(e&&typeof e===o)for(var r in e)typeof a.flashvars!=l?a.flashvars+="&"+r+"="+e[r]:a.flashvars=r+"="+e[r];if(t(k))b=s(p,a,b),j.success=!0,j.ref=b}h&&h(j)},ua:g,getFlashPlayerVersion:function(){return{major:g.pv[0],minor:g.pv[1],release:g.pv[2]}},hasFlashPlayerVersion:t,createSWF:function(a,b,d){if(g.w3)return s(a,b,d)},getQueryParamValue:function(a){var b= a&&b&&d&&i&&k){d+="";i+="";var p={};if(f&&typeof f===o)for(var m in f)p[m]=f[m];p.data=a;p.width=d;p.height=i;a={};if(c&&typeof c===o)for(var n in c)a[n]=c[n];if(e&&typeof e===o)for(var r in e)typeof a.flashvars!=l?a.flashvars+="&"+r+"="+e[r]:a.flashvars=r+"="+e[r];if(t(k))b=s(p,a,b),j.success=!0,j.ref=b}h&&h(j)},ua:g,getFlashPlayerVersion:function(){return{major:g.pv[0],minor:g.pv[1],release:g.pv[2]}},hasFlashPlayerVersion:t,createSWF:function(a,b,d){if(g.w3)return s(a,b,d)},getQueryParamValue:function(a){var b=
i.location.search||i.location.hash;if(b){/\?/.test(b)&&(b=b.split("?")[1]);if(a==null)return u(b);for(var b=b.split("&"),d=0;d<b.length;d++)if(b[d].substring(0,b[d].indexOf("="))==a)return u(b[d].substring(b[d].indexOf("=")+1))}return""}}}(); i.location.search||i.location.hash;if(b){/\?/.test(b)&&(b=b.split("?")[1]);if(a==null)return u(b);for(var b=b.split("&"),d=0;d<b.length;d++)if(b[d].substring(0,b[d].indexOf("="))==a)return u(b[d].substring(b[d].indexOf("=")+1))}return""}}}();

View File

@ -12,6 +12,7 @@ group :development do
gem 'rubypants' gem 'rubypants'
gem 'rb-fsevent' gem 'rb-fsevent'
gem 'stringex' gem 'stringex'
gem 'liquid', '2.2.2'
end end
gem 'sinatra', '1.2.6' gem 'sinatra', '1.2.6'

View File

@ -55,6 +55,7 @@ DEPENDENCIES
compass (>= 0.11) compass (>= 0.11)
haml (>= 3.1) haml (>= 3.1)
jekyll jekyll
liquid (= 2.2.2)
pygments.rb pygments.rb
rack rack
rake rake

View File

@ -3,15 +3,22 @@
Octopress is [Jekyll](https://github.com/mojombo/jekyll) blogging at its finest. Octopress is [Jekyll](https://github.com/mojombo/jekyll) blogging at its finest.
1. **Octopress sports a clean responsive theme** written in semantic HTML5, focused on readability and friendliness toward mobile devices. 1. **Octopress sports a clean responsive theme** written in semantic HTML5, focused on readability and friendliness toward mobile devices.
2. **Code blogging is easy and beautiful.** Embed code (with [Solarized](http://ethanschoonover.com/solarized) styling) in your posts from gists or from your filesystem. 2. **Code blogging is easy and beautiful.** Embed code (with [Solarized](http://ethanschoonover.com/solarized) styling) in your posts from gists, jsFiddle or from your filesystem.
3. **Third party integration is simple** with built-in support for Twitter, Pinboard, Delicious, Disqus Comments, and Google Analytics. 3. **Third party integration is simple** with built-in support for Twitter, Pinboard, Delicious, GitHub Repositories, Disqus Comments and Google Analytics.
4. **It's easy to use.** A collection of rake tasks simplifies development and makes deploying a cinch. 4. **It's easy to use.** A collection of rake tasks simplifies development and makes deploying a cinch.
5. **Ships with great plugins** some original and others from the Jekyll community &mdash; tested and improved. 5. **Ships with great plug-ins** some original and others from the Jekyll community &mdash; tested and improved.
## Documentation ## Documentation
Check out [Octopress.org](http://octopress.org/docs) for guides and documentation. Check out [Octopress.org](http://octopress.org/docs) for guides and documentation.
## Contributing
We love to see people contributing to Octopress, whether it's a bug report, feature suggestion or a pull request. At the moment, we try to keep the core slick and lean, focusing on basic blogging needs, so some of your suggestions might not find their way into Octopress. For those ideas, we started a [list of 3rd party plug-ins](https://github.com/imathis/octopress/wiki/3rd-party-plug-ins), where you can link your own Octopress plug-in repositories. For the future, we're thinking about ways to easier add them them into our main releases.
## License ## License
(The MIT License) (The MIT License)
@ -23,6 +30,7 @@ The above copyright notice and this permission notice shall be included in all c
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#### If you want to be awesome. #### If you want to be awesome.
- Proudly display the 'Powered by Octopress' credit in the footer. - Proudly display the 'Powered by Octopress' credit in the footer.
- Add your site to the wiki so we can watch the community grow. - Add your site to the Wiki so we can watch the community grow.

View File

@ -7,11 +7,11 @@ require "stringex"
ssh_user = "imathis@octopress.org" ssh_user = "imathis@octopress.org"
document_root = "~/octopress.org/" document_root = "~/octopress.org/"
ssh_port = "22" ssh_port = "22"
rsync_delete = true
deploy_default = "rsync" deploy_default = "push"
# This will be configured for you when you run config_deploy # This will be configured for you when you run config_deploy
deploy_branch = "gh-pages" deploy_branch = "master"
## -- Misc Configs -- ## ## -- Misc Configs -- ##
@ -59,7 +59,8 @@ desc "Watch the site and regenerate when it changes"
task :watch do task :watch do
raise "### You haven't set anything up yet. First run `rake install` to set up an Octopress theme." unless File.directory?(source_dir) raise "### You haven't set anything up yet. First run `rake install` to set up an Octopress theme." unless File.directory?(source_dir)
puts "Starting to watch source with Jekyll and Compass." puts "Starting to watch source with Jekyll and Compass."
jekyllPid = Process.spawn("jekyll --auto") system "compass compile --css-dir #{source_dir}/stylesheets" unless File.exist?("#{source_dir}/stylesheets/screen.css")
jekyllPid = Process.spawn({"OCTOPRESS_ENV"=>"preview"}, "jekyll --auto")
compassPid = Process.spawn("compass watch") compassPid = Process.spawn("compass watch")
trap("INT") { trap("INT") {
@ -74,7 +75,8 @@ desc "preview the site in a web browser"
task :preview do task :preview do
raise "### You haven't set anything up yet. First run `rake install` to set up an Octopress theme." unless File.directory?(source_dir) raise "### You haven't set anything up yet. First run `rake install` to set up an Octopress theme." unless File.directory?(source_dir)
puts "Starting to watch source with Jekyll and Compass. Starting Rack on port #{server_port}" puts "Starting to watch source with Jekyll and Compass. Starting Rack on port #{server_port}"
jekyllPid = Process.spawn("jekyll --auto") system "compass compile --css-dir #{source_dir}/stylesheets" unless File.exist?("#{source_dir}/stylesheets/screen.css")
jekyllPid = Process.spawn({"OCTOPRESS_ENV"=>"preview"}, "jekyll --auto")
compassPid = Process.spawn("compass watch") compassPid = Process.spawn("compass watch")
rackupPid = Process.spawn("rackup --port #{server_port}") rackupPid = Process.spawn("rackup --port #{server_port}")
@ -90,7 +92,6 @@ end
desc "Begin a new post in #{source_dir}/#{posts_dir}" desc "Begin a new post in #{source_dir}/#{posts_dir}"
task :new_post, :title do |t, args| task :new_post, :title do |t, args|
raise "### You haven't set anything up yet. First run `rake install` to set up an Octopress theme." unless File.directory?(source_dir) raise "### You haven't set anything up yet. First run `rake install` to set up an Octopress theme." unless File.directory?(source_dir)
require './plugins/titlecase.rb'
mkdir_p "#{source_dir}/#{posts_dir}" mkdir_p "#{source_dir}/#{posts_dir}"
args.with_defaults(:title => 'new-post') args.with_defaults(:title => 'new-post')
title = args.title title = args.title
@ -100,10 +101,9 @@ task :new_post, :title do |t, args|
end end
puts "Creating new post: #{filename}" puts "Creating new post: #{filename}"
open(filename, 'w') do |post| open(filename, 'w') do |post|
system "mkdir -p #{source_dir}/#{posts_dir}/";
post.puts "---" post.puts "---"
post.puts "layout: post" post.puts "layout: post"
post.puts "title: \"#{title.gsub(/&/,'&amp;').titlecase}\"" post.puts "title: \"#{title.gsub(/&/,'&amp;')}\""
post.puts "date: #{Time.now.strftime('%Y-%m-%d %H:%M')}" post.puts "date: #{Time.now.strftime('%Y-%m-%d %H:%M')}"
post.puts "comments: true" post.puts "comments: true"
post.puts "categories: " post.puts "categories: "
@ -115,16 +115,22 @@ end
desc "Create a new page in #{source_dir}/(filename)/index.#{new_page_ext}" desc "Create a new page in #{source_dir}/(filename)/index.#{new_page_ext}"
task :new_page, :filename do |t, args| task :new_page, :filename do |t, args|
raise "### You haven't set anything up yet. First run `rake install` to set up an Octopress theme." unless File.directory?(source_dir) raise "### You haven't set anything up yet. First run `rake install` to set up an Octopress theme." unless File.directory?(source_dir)
require './plugins/titlecase.rb'
args.with_defaults(:filename => 'new-page') args.with_defaults(:filename => 'new-page')
page_dir = source_dir page_dir = [source_dir]
if args.filename =~ /(^.+\/)?([\w_-]+)(\.)?(.+)?/ if args.filename.downcase =~ /(^.+\/)?(.+)/
page_dir += $4 ? "/#{$1}" : "/#{$1}#{$2}/" filename, dot, extension = $2.rpartition('.').reject(&:empty?) # Get filename and extension
name = $4 ? $2 : "index" title = filename
extension = $4 || "#{new_page_ext}" page_dir.concat($1.downcase.sub(/^\//, '').split('/')) unless $1.nil? # Add path to page_dir Array
filename = "#{name}.#{extension}" if extension.nil?
page_dir << filename
filename = "index"
end
extension ||= new_page_ext
page_dir = page_dir.map! { |d| d = d.to_url }.join('/') # Sanitize path
filename = filename.downcase.to_url
mkdir_p page_dir mkdir_p page_dir
file = page_dir + filename file = "#{page_dir}/#{filename}.#{extension}"
if File.exist?(file) if File.exist?(file)
abort("rake aborted!") if ask("#{file} already exists. Do you want to overwrite?", ['y', 'n']) == 'n' abort("rake aborted!") if ask("#{file} already exists. Do you want to overwrite?", ['y', 'n']) == 'n'
end end
@ -132,7 +138,7 @@ task :new_page, :filename do |t, args|
open(file, 'w') do |page| open(file, 'w') do |page|
page.puts "---" page.puts "---"
page.puts "layout: page" page.puts "layout: page"
page.puts "title: \"#{$2.gsub(/[-_]/, ' ').titlecase}\"" page.puts "title: \"#{title}\""
page.puts "date: #{Time.now.strftime('%Y-%m-%d %H:%M')}" page.puts "date: #{Time.now.strftime('%Y-%m-%d %H:%M')}"
page.puts "comments: true" page.puts "comments: true"
page.puts "sharing: true" page.puts "sharing: true"
@ -185,12 +191,14 @@ task :update_source, :theme do |t, args|
puts "## Removed existing #{source_dir}.old directory" puts "## Removed existing #{source_dir}.old directory"
rm_r "#{source_dir}.old", :secure=>true rm_r "#{source_dir}.old", :secure=>true
end end
mkdir "#{source_dir}.old"
cp_r "#{source_dir}/.", "#{source_dir}.old" cp_r "#{source_dir}/.", "#{source_dir}.old"
puts "## Copied #{source_dir} into #{source_dir}.old/" puts "## Copied #{source_dir} into #{source_dir}.old/"
cp_r "#{themes_dir}/"+theme+"/source/.", source_dir, :remove_destination=>true cp_r "#{themes_dir}/"+theme+"/source/.", source_dir, :remove_destination=>true
cp_r "#{source_dir}.old/_includes/custom/.", "#{source_dir}/_includes/custom/", :remove_destination=>true cp_r "#{source_dir}.old/_includes/custom/.", "#{source_dir}/_includes/custom/", :remove_destination=>true
cp "#{source_dir}.old/favicon.png", source_dir
mv "#{source_dir}/index.html", "#{blog_index_dir}", :force=>true if blog_index_dir != source_dir mv "#{source_dir}/index.html", "#{blog_index_dir}", :force=>true if blog_index_dir != source_dir
cp "#{source_dir}.old/index.html", source_dir if blog_index_dir != source_dir cp "#{source_dir}.old/index.html", source_dir if blog_index_dir != source_dir && File.exists?("#{source_dir}.old/index.html")
puts "## Updated #{source_dir} ##" puts "## Updated #{source_dir} ##"
end end
@ -200,6 +208,13 @@ end
desc "Default deploy task" desc "Default deploy task"
task :deploy do task :deploy do
# Check if preview posts exist, which should not be published
if File.exists?(".preview-mode")
puts "## Found posts in preview mode, regenerating files ..."
File.delete(".preview-mode")
Rake::Task[:generate].execute
end
Rake::Task[:copydot].invoke(source_dir, public_dir) Rake::Task[:copydot].invoke(source_dir, public_dir)
Rake::Task["#{deploy_default}"].execute Rake::Task["#{deploy_default}"].execute
end end
@ -210,18 +225,19 @@ end
desc "copy dot files for deployment" desc "copy dot files for deployment"
task :copydot, :source, :dest do |t, args| task :copydot, :source, :dest do |t, args|
exclusions = [".", "..", ".DS_Store"] FileList["#{args.source}/**/.*"].exclude("**/.", "**/..", "**/.DS_Store", "**/._*").each do |file|
Dir["#{args.source}/**/.*"].each do |file| cp_r file, file.gsub(/#{args.source}/, "#{args.dest}") unless File.directory?(file)
if !File.directory?(file) && !exclusions.include?(File.basename(file))
cp(file, file.gsub(/#{args.source}/, "#{args.dest}"));
end
end end
end end
desc "Deploy website via rsync" desc "Deploy website via rsync"
task :rsync do task :rsync do
exclude = ""
if File.exists?('./rsync-exclude')
exclude = "--exclude-from '#{File.expand_path('./rsync-exclude')}'"
end
puts "## Deploying website via Rsync" puts "## Deploying website via Rsync"
ok_failed system("rsync -avze 'ssh -p #{ssh_port}' --delete #{public_dir}/ #{ssh_user}:#{document_root}") ok_failed system("rsync -avze 'ssh -p #{ssh_port}' #{exclude} #{"--delete" unless rsync_delete == false} #{public_dir}/ #{ssh_user}:#{document_root}")
end end
desc "deploy public directory to github pages" desc "deploy public directory to github pages"
@ -230,7 +246,7 @@ multitask :push do
(Dir["#{deploy_dir}/*"]).each { |f| rm_rf(f) } (Dir["#{deploy_dir}/*"]).each { |f| rm_rf(f) }
Rake::Task[:copydot].invoke(public_dir, deploy_dir) Rake::Task[:copydot].invoke(public_dir, deploy_dir)
puts "\n## copying #{public_dir} to #{deploy_dir}" puts "\n## copying #{public_dir} to #{deploy_dir}"
system "cp -R #{public_dir}/* #{deploy_dir}" cp_r "#{public_dir}/.", deploy_dir
cd "#{deploy_dir}" do cd "#{deploy_dir}" do
system "git add ." system "git add ."
system "git add -u" system "git add -u"
@ -279,10 +295,14 @@ task :set_root_dir, :dir do |t, args|
end end
desc "Set up _deploy folder and deploy branch for Github Pages deployment" desc "Set up _deploy folder and deploy branch for Github Pages deployment"
task :setup_github_pages do task :setup_github_pages, :repo do |t, args|
repo_url = get_stdin("Enter the read/write url for your repository: ") if args.repo
repo_url = args.repo
else
repo_url = get_stdin("Enter the read/write url for your repository: ")
end
user = repo_url.match(/:([^\/]+)/)[1] user = repo_url.match(/:([^\/]+)/)[1]
branch = (repo_url.match(/\/\w+.github.com/).nil?) ? 'gh-pages' : 'master' branch = (repo_url.match(/\/[\w-]+.github.com/).nil?) ? 'gh-pages' : 'master'
project = (branch == 'gh-pages') ? repo_url.match(/\/([^\.]+)/)[1] : '' project = (branch == 'gh-pages') ? repo_url.match(/\/([^\.]+)/)[1] : ''
unless `git remote -v`.match(/origin.+?octopress.git/).nil? unless `git remote -v`.match(/origin.+?octopress.git/).nil?
# If octopress is still the origin remote (from cloning) rename it to octopress # If octopress is still the origin remote (from cloning) rename it to octopress

View File

@ -7,6 +7,13 @@ title: Octopress
subtitle: A blogging framework for hackers. subtitle: A blogging framework for hackers.
author: Brandon Mathis author: Brandon Mathis
simple_search: http://google.com/search simple_search: http://google.com/search
description:
# Default date format is "ordinal" (resulting in "July 22nd 2007")
# You can customize the format as defined in
# http://www.ruby-doc.org/core-1.9.2/Time.html#method-i-strftime
# Additionally, %o will give you the ordinal representation of the day
date_format: "ordinal"
# RSS / Email (optional) subscription links (change if using something like Feedburner) # RSS / Email (optional) subscription links (change if using something like Feedburner)
subscribe_rss: /atom.xml subscribe_rss: /atom.xml
@ -35,9 +42,11 @@ pagination_dir: blog # Directory base for pagination URLs eg. /blog/page/2/
recent_posts: 5 # Posts in the sidebar Recent Posts section recent_posts: 5 # Posts in the sidebar Recent Posts section
excerpt_link: "Read on &rarr;" # "Continue reading" link text at the bottom of excerpted articles excerpt_link: "Read on &rarr;" # "Continue reading" link text at the bottom of excerpted articles
titlecase: true # Converts page and post titles to tilecase
# list each of the sidebar modules you want to include, in the order you want them to appear. # list each of the sidebar modules you want to include, in the order you want them to appear.
# To add custom asides, create files in /source/_includes/custom/asides/ and add them to the list like 'custom/asides/custom_aside_name.html' # To add custom asides, create files in /source/_includes/custom/asides/ and add them to the list like 'custom/asides/custom_aside_name.html'
default_asides: [asides/recent_posts.html, asides/github.html, asides/twitter.html, asides/delicious.html, asides/pinboard.html] default_asides: [asides/recent_posts.html, asides/github.html, asides/twitter.html, asides/delicious.html, asides/pinboard.html, asides/googleplus.html]
# Each layout uses the default asides, but they can have their own asides instead. Simply uncomment the lines below # Each layout uses the default asides, but they can have their own asides instead. Simply uncomment the lines below
# and add an array with the asides you want to use. # and add an array with the asides you want to use.
@ -63,10 +72,15 @@ twitter_follow_button: true
twitter_show_follower_count: false twitter_show_follower_count: false
twitter_tweet_button: true twitter_tweet_button: true
# Google Plus # Google +1
google_plus_one: true google_plus_one: false
google_plus_one_size: medium google_plus_one_size: medium
# Google Plus Profile
# Hidden: No visible button, just add author information to search results
googleplus_user:
googleplus_hidden: false
# Pinboard # Pinboard
pinboard_user: pinboard_user:
pinboard_count: 4 pinboard_count: 4
@ -81,3 +95,6 @@ disqus_show_comment_count: false
# Google Analytics # Google Analytics
google_analytics_tracking_id: UA-10876422-2 google_analytics_tracking_id: UA-10876422-2
# Facebook Like
facebook_like: true

View File

@ -10,7 +10,7 @@ module BacktickCodeBlock
@lang = nil @lang = nil
@url = nil @url = nil
@title = nil @title = nil
input.gsub /^`{3} *([^\n]+)?\n(.+?)\n`{3}/m do input.gsub(/^`{3} *([^\n]+)?\n(.+?)\n`{3}/m) do
@options = $1 || '' @options = $1 || ''
str = $2 str = $2
@ -22,8 +22,8 @@ module BacktickCodeBlock
@caption = "<figcaption><span>#{$2}</span></figcaption>" @caption = "<figcaption><span>#{$2}</span></figcaption>"
end end
if str.match(/\A {4}/) if str.match(/\A( {4}|\t)/)
str = str.gsub /^ {4}/, '' str = str.gsub(/^( {4}|\t)/, '')
end end
if @lang.nil? || @lang == 'plain' if @lang.nil? || @lang == 'plain'
code = tableize_code(str.gsub('<','&lt;').gsub('>','&gt;')) code = tableize_code(str.gsub('<','&lt;').gsub('>','&gt;'))

View File

@ -60,9 +60,9 @@ module Jekyll
source << '/&hellip;' unless source == @source source << '/&hellip;' unless source == @source
end end
if !@source.nil? if !@source.nil?
cite = "<cite><a href='#{@source}'>#{(@title || source)}</a></cite>" cite = " <cite><a href='#{@source}'>#{(@title || source)}</a></cite>"
elsif !@title.nil? elsif !@title.nil?
cite = "<cite>#{@title}</cite>" cite = " <cite>#{@title}</cite>"
end end
blockquote = if @by.nil? blockquote = if @by.nil?
quote quote

View File

@ -48,6 +48,35 @@ module Jekyll
end end
# The CategoryFeed class creates an Atom feed for the specified category.
class CategoryFeed < Page
# Initializes a new CategoryFeed.
#
# +base+ is the String path to the <source>.
# +category_dir+ is the String path between <source> and the category folder.
# +category+ is the category currently being processed.
def initialize(site, base, category_dir, category)
@site = site
@base = base
@dir = category_dir
@name = 'atom.xml'
self.process(@name)
# Read the YAML data from the layout page.
self.read_yaml(File.join(base, '_includes/custom'), 'category_feed.xml')
self.data['category'] = category
# Set the title for this page.
title_prefix = site.config['category_title_prefix'] || 'Category: '
self.data['title'] = "#{title_prefix}#{category}"
# Set the meta-description for this page.
meta_description_prefix = site.config['category_meta_description_prefix'] || 'Category: '
self.data['description'] = "#{meta_description_prefix}#{category}"
# Set the correct feed URL.
self.data['feed_url'] = "#{category_dir}/#{name}"
end
end
# The Site class is a built-in Jekyll class with access to global site config information. # The Site class is a built-in Jekyll class with access to global site config information.
class Site class Site
@ -63,6 +92,13 @@ module Jekyll
index.write(self.dest) index.write(self.dest)
# Record the fact that this page has been added, otherwise Site::cleanup will remove it. # Record the fact that this page has been added, otherwise Site::cleanup will remove it.
self.pages << index self.pages << index
# Create an Atom-feed for each index.
feed = CategoryFeed.new(self, self.source, category_dir, category)
feed.render(self.layouts, site_payload)
feed.write(self.dest)
# Record the fact that this page has been added, otherwise Site::cleanup will remove it.
self.pages << feed
end end
# Loops through the list of category pages and processes each one. # Loops through the list of category pages and processes each one.

View File

@ -90,6 +90,7 @@ module Jekyll
source = safe_wrap(source) source = safe_wrap(source)
source = context['pygments_prefix'] + source if context['pygments_prefix'] source = context['pygments_prefix'] + source if context['pygments_prefix']
source = source + context['pygments_suffix'] if context['pygments_suffix'] source = source + context['pygments_suffix'] if context['pygments_suffix']
source
end end
end end
end end

98
plugins/date.rb Normal file
View File

@ -0,0 +1,98 @@
module Octopress
module Date
# Returns a datetime if the input is a string
def datetime(date)
if date.class == String
date = Time.parse(date)
end
date
end
# Returns an ordidinal date eg July 22 2007 -> July 22nd 2007
def ordinalize(date)
date = datetime(date)
"#{date.strftime('%b')} #{ordinal(date.strftime('%e').to_i)}, #{date.strftime('%Y')}"
end
# Returns an ordinal number. 13 -> 13th, 21 -> 21st etc.
def ordinal(number)
if (11..13).include?(number.to_i % 100)
"#{number}<span>th</span>"
else
case number.to_i % 10
when 1; "#{number}<span>st</span>"
when 2; "#{number}<span>nd</span>"
when 3; "#{number}<span>rd</span>"
else "#{number}<span>th</span>"
end
end
end
# Formats date either as ordinal or by given date format
# Adds %o as ordinal representation of the day
def format_date(date, format)
date = datetime(date)
if format.nil? || format.empty? || format == "ordinal"
date_formatted = ordinalize(date)
else
date_formatted = date.strftime(format)
date_formatted.gsub!(/%o/, ordinal(date.strftime('%e').to_i))
end
date_formatted
end
end
end
module Jekyll
class Post
include Octopress::Date
# Convert this post into a Hash for use in Liquid templates.
#
# Returns <Hash>
def to_liquid
date_format = self.site.config['date_format']
self.data.deep_merge({
"title" => self.data['title'] || self.slug.split('-').select {|w| w.capitalize! || w }.join(' '),
"url" => self.url,
"date" => self.date,
# Monkey patch
"date_formatted" => format_date(self.date, date_format),
"updated_formatted" => self.data.has_key?('updated') ? format_date(self.data['updated'], date_format) : nil,
"id" => self.id,
"categories" => self.categories,
"next" => self.next,
"previous" => self.previous,
"tags" => self.tags,
"content" => self.content })
end
end
class Page
include Octopress::Date
# Initialize a new Page.
#
# site - The Site object.
# base - The String path to the source.
# dir - The String path between the source and the file.
# name - The String filename of the file.
def initialize(site, base, dir, name)
@site = site
@base = base
@dir = dir
@name = name
self.process(name)
self.read_yaml(File.join(base, dir), name)
# Monkey patch
date_format = self.site.config['date_format']
self.data['date_formatted'] = format_date(self.data['date'], date_format) if self.data.has_key?('date')
self.data['updated_formatted'] = format_date(self.data['updated'], date_format) if self.data.has_key?('updated')
end
end
end

View File

@ -71,7 +71,13 @@ module Jekyll
def get_gist_from_web(gist, file) def get_gist_from_web(gist, file)
gist_url = get_gist_url_for gist, file gist_url = get_gist_url_for gist, file
raw_uri = URI.parse gist_url raw_uri = URI.parse gist_url
https = Net::HTTP.new raw_uri.host, raw_uri.port proxy = ENV['http_proxy']
if proxy
proxy_uri = URI.parse(proxy)
https = Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port).new raw_uri.host, raw_uri.port
else
https = Net::HTTP.new raw_uri.host, raw_uri.port
end
https.use_ssl = true https.use_ssl = true
https.verify_mode = OpenSSL::SSL::VERIFY_NONE https.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Get.new raw_uri.request_uri request = Net::HTTP::Get.new raw_uri.request_uri

View File

@ -2,6 +2,7 @@
require './plugins/backtick_code_block' require './plugins/backtick_code_block'
require './plugins/post_filters' require './plugins/post_filters'
require './plugins/raw' require './plugins/raw'
require './plugins/date'
require 'rubypants' require 'rubypants'
module OctopressFilters module OctopressFilters
@ -33,6 +34,8 @@ end
module OctopressLiquidFilters module OctopressLiquidFilters
include Octopress::Date
# Used on the blog index to split posts on the <!--more--> marker # Used on the blog index to split posts on the <!--more--> marker
def excerpt(input) def excerpt(input)
if input.index(/<!--\s*more\s*-->/i) if input.index(/<!--\s*more\s*-->/i)
@ -63,6 +66,11 @@ module OctopressLiquidFilters
return (content.nil?) ? input : content return (content.nil?) ? input : content
end end
# Escapes CDATA sections in post content
def cdata_escape(input)
input.gsub(/<!\[CDATA\[/, '&lt;![CDATA[').gsub(/\]\]>/, ']]&gt;')
end
# Replaces relative urls with full urls # Replaces relative urls with full urls
def expand_urls(input, url='') def expand_urls(input, url='')
url ||= '/' url ||= '/'
@ -71,6 +79,33 @@ module OctopressLiquidFilters
end end
end end
# Improved version of Liquid's truncate:
# - Doesn't cut in the middle of a word.
# - Uses typographically correct ellipsis (…) insted of '...'
def truncate(input, length)
if input.length > length && input[0..(length-1)] =~ /(.+)\b.+$/im
$1.strip + ' &hellip;'
else
input
end
end
# Improved version of Liquid's truncatewords:
# - Uses typographically correct ellipsis (…) insted of '...'
def truncatewords(input, length)
truncate = input.split(' ')
if truncate.length > length
truncate[0..length-1].join(' ').strip + ' &hellip;'
else
input
end
end
# Condenses multiple spaces and tabs into a single space
def condense_spaces(input)
input.gsub(/\s{2,}/, ' ')
end
# Removes trailing forward slash from a string for easily appending url segments # Removes trailing forward slash from a string for easily appending url segments
def strip_slash(input) def strip_slash(input)
if input =~ /(.+)\/$|^\/$/ if input =~ /(.+)\/$|^\/$/
@ -91,33 +126,6 @@ module OctopressLiquidFilters
input.titlecase input.titlecase
end end
# Returns a datetime if the input is a string
def datetime(date)
if date.class == String
date = Time.parse(date)
end
date
end
# Returns an ordidinal date eg July 22 2007 -> July 22nd 2007
def ordinalize(date)
date = datetime(date)
"#{date.strftime('%b')} #{ordinal(date.strftime('%e').to_i)}, #{date.strftime('%Y')}"
end
# Returns an ordinal number. 13 -> 13th, 21 -> 21st etc.
def ordinal(number)
if (11..13).include?(number.to_i % 100)
"#{number}<span>th</span>"
else
case number.to_i % 10
when 1; "#{number}<span>st</span>"
when 2; "#{number}<span>nd</span>"
when 3; "#{number}<span>rd</span>"
else "#{number}<span>th</span>"
end
end
end
end end
Liquid::Template.register_filter OctopressLiquidFilters Liquid::Template.register_filter OctopressLiquidFilters

View File

@ -0,0 +1,48 @@
# Monkeypatch for Jekyll
# Introduce distinction between preview/productive site generation
# so posts with YAML attribute `published: false` can be previewed
# on localhost without being published to the productive environment.
module Jekyll
class Site
# Read all the files in <source>/<dir>/_posts and create a new Post
# object with each one.
#
# dir - The String relative path of the directory to read.
#
# Returns nothing.
def read_posts(dir)
base = File.join(self.source, dir, '_posts')
return unless File.exists?(base)
entries = Dir.chdir(base) { filter_entries(Dir['**/*']) }
# first pass processes, but does not yet render post content
entries.each do |f|
if Post.valid?(f)
post = Post.new(self, self.source, dir, f)
# Monkeypatch:
# On preview environment (localhost), publish all posts
if ENV.has_key?('OCTOPRESS_ENV') && ENV['OCTOPRESS_ENV'] == 'preview' && post.data.has_key?('published') && post.data['published'] == false
post.published = true
# Set preview mode flag (if necessary), `rake generate` will check for it
# to prevent pushing preview posts to productive environment
File.open(".preview-mode", "w") {}
end
if post.published && (self.future || post.date <= self.time)
self.posts << post
post.categories.each { |c| self.categories[c] << post }
post.tags.each { |c| self.tags[c] << post }
end
end
end
self.posts.sort!
# limit the posts if :limit_posts option is set
self.posts = self.posts[-limit_posts, limit_posts] if limit_posts
end
end
end

View File

@ -1,6 +1,6 @@
# #
# Author: Brandon Mathis # Author: Brandon Mathis
# Based on the sematic pullquote technique by Maykel Loomans at http://miekd.com/articles/pull-quotes-with-html5-and-css/ # Based on the semantic pullquote technique by Maykel Loomans at http://miekd.com/articles/pull-quotes-with-html5-and-css/
# #
# Outputs a span with a data-pullquote attribute set from the marked pullquote. Example: # Outputs a span with a data-pullquote attribute set from the marked pullquote. Example:
# #
@ -13,23 +13,30 @@
# <p> # <p>
# <span data-pullquote="pullquotes are merely visual in presentation and should not appear twice in the text."> # <span data-pullquote="pullquotes are merely visual in presentation and should not appear twice in the text.">
# When writing longform posts, I find it helpful to include pullquotes, which help those scanning a post discern whether or not a post is helpful. # When writing longform posts, I find it helpful to include pullquotes, which help those scanning a post discern whether or not a post is helpful.
# It is important to note, pullquotes are merely visual in presentation and should not appear twice in the text. This is why a CSS only approach # for styling pullquotes is prefered. # It is important to note, pullquotes are merely visual in presentation and should not appear twice in the text. This is why a CSS only approach
# for styling pullquotes is prefered.
# </span> # </span>
# </p> # </p>
# #
# {% pullquote left %} will create a left-aligned pullquote instead.
#
# Note: this plugin now creates pullquotes with the class of pullquote-right by default
module Jekyll module Jekyll
class PullquoteTag < Liquid::Block class PullquoteTag < Liquid::Block
def initialize(tag_name, markup, tokens) def initialize(tag_name, markup, tokens)
@align = (markup =~ /left/i) ? "left" : "right"
super super
end end
def render(context) def render(context)
output = super output = super
if output.join =~ /\{"\s*(.+)\s*"\}/ if output.join =~ /\{"\s*(.+)\s*"\}/
@quote = $1 #@quote = $1
"<span class='has-pullquote' data-pullquote='#{@quote}'>#{output.join.gsub(/\{"\s*|\s*"\}/, '')}</span>" @quote = RubyPants.new($1).to_html
#@quote = CGI.escape($1)
"<span class='pullquote-#{@align}' data-pullquote='#{@quote}'>#{output.join.gsub(/\{"\s*|\s*"\}/, '')}</span>"
else else
return "Surround your pullquote like this {\" text to be quoted \"}" return "Surround your pullquote like this {\" text to be quoted \"}"
end end

View File

@ -21,11 +21,11 @@ module HighlightCode
if File.exist?(path) if File.exist?(path)
highlighted_code = File.read(path) highlighted_code = File.read(path)
else else
highlighted_code = Pygments.highlight(code, :lexer => lang, :formatter => 'html') highlighted_code = Pygments.highlight(code, :lexer => lang, :formatter => 'html', :options => {:encoding => 'utf-8'})
File.open(path, 'w') {|f| f.print(highlighted_code) } File.open(path, 'w') {|f| f.print(highlighted_code) }
end end
else else
highlighted_code = Pygments.highlight(code, :lexer => lang, :formatter => 'html') highlighted_code = Pygments.highlight(code, :lexer => lang, :formatter => 'html', :options => {:encoding => 'utf-8'})
end end
highlighted_code highlighted_code
end end

489
plugins/rubypants.rb Normal file
View File

@ -0,0 +1,489 @@
#
# = RubyPants -- SmartyPants ported to Ruby
#
# Ported by Christian Neukirchen <mailto:chneukirchen@gmail.com>
# Copyright (C) 2004 Christian Neukirchen
#
# Incooporates ideas, comments and documentation by Chad Miller
# Copyright (C) 2004 Chad Miller
#
# Original SmartyPants by John Gruber
# Copyright (C) 2003 John Gruber
#
#
# = RubyPants -- SmartyPants ported to Ruby
#
# == Synopsis
#
# RubyPants is a Ruby port of the smart-quotes library SmartyPants.
#
# The original "SmartyPants" is a free web publishing plug-in for
# Movable Type, Blosxom, and BBEdit that easily translates plain ASCII
# punctuation characters into "smart" typographic punctuation HTML
# entities.
#
#
# == Description
#
# RubyPants can perform the following transformations:
#
# * Straight quotes (<tt>"</tt> and <tt>'</tt>) into "curly" quote
# HTML entities
# * Backticks-style quotes (<tt>``like this''</tt>) into "curly" quote
# HTML entities
# * Dashes (<tt>--</tt> and <tt>---</tt>) into en- and em-dash
# entities
# * Three consecutive dots (<tt>...</tt> or <tt>. . .</tt>) into an
# ellipsis entity
#
# This means you can write, edit, and save your posts using plain old
# ASCII straight quotes, plain dashes, and plain dots, but your
# published posts (and final HTML output) will appear with smart
# quotes, em-dashes, and proper ellipses.
#
# RubyPants does not modify characters within <tt><pre></tt>,
# <tt><code></tt>, <tt><kbd></tt>, <tt><math></tt> or
# <tt><script></tt> tag blocks. Typically, these tags are used to
# display text where smart quotes and other "smart punctuation" would
# not be appropriate, such as source code or example markup.
#
#
# == Backslash Escapes
#
# If you need to use literal straight quotes (or plain hyphens and
# periods), RubyPants accepts the following backslash escape sequences
# to force non-smart punctuation. It does so by transforming the
# escape sequence into a decimal-encoded HTML entity:
#
# \\ \" \' \. \- \`
#
# This is useful, for example, when you want to use straight quotes as
# foot and inch marks: 6'2" tall; a 17" iMac. (Use <tt>6\'2\"</tt>
# resp. <tt>17\"</tt>.)
#
#
# == Algorithmic Shortcomings
#
# One situation in which quotes will get curled the wrong way is when
# apostrophes are used at the start of leading contractions. For
# example:
#
# 'Twas the night before Christmas.
#
# In the case above, RubyPants will turn the apostrophe into an
# opening single-quote, when in fact it should be a closing one. I
# don't think this problem can be solved in the general case--every
# word processor I've tried gets this wrong as well. In such cases,
# it's best to use the proper HTML entity for closing single-quotes
# ("<tt>&#8217;</tt>") by hand.
#
#
# == Bugs
#
# To file bug reports or feature requests (except see above) please
# send email to: mailto:chneukirchen@gmail.com
#
# If the bug involves quotes being curled the wrong way, please send
# example text to illustrate.
#
#
# == Authors
#
# John Gruber did all of the hard work of writing this software in
# Perl for Movable Type and almost all of this useful documentation.
# Chad Miller ported it to Python to use with Pyblosxom.
#
# Christian Neukirchen provided the Ruby port, as a general-purpose
# library that follows the *Cloth API.
#
#
# == Copyright and License
#
# === SmartyPants license:
#
# Copyright (c) 2003 John Gruber
# (http://daringfireball.net)
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# * Neither the name "SmartyPants" nor the names of its contributors
# may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# This software is provided by the copyright holders and contributors
# "as is" and any express or implied warranties, including, but not
# limited to, the implied warranties of merchantability and fitness
# for a particular purpose are disclaimed. In no event shall the
# copyright owner or contributors be liable for any direct, indirect,
# incidental, special, exemplary, or consequential damages (including,
# but not limited to, procurement of substitute goods or services;
# loss of use, data, or profits; or business interruption) however
# caused and on any theory of liability, whether in contract, strict
# liability, or tort (including negligence or otherwise) arising in
# any way out of the use of this software, even if advised of the
# possibility of such damage.
#
# === RubyPants license
#
# RubyPants is a derivative work of SmartyPants and smartypants.py.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# This software is provided by the copyright holders and contributors
# "as is" and any express or implied warranties, including, but not
# limited to, the implied warranties of merchantability and fitness
# for a particular purpose are disclaimed. In no event shall the
# copyright owner or contributors be liable for any direct, indirect,
# incidental, special, exemplary, or consequential damages (including,
# but not limited to, procurement of substitute goods or services;
# loss of use, data, or profits; or business interruption) however
# caused and on any theory of liability, whether in contract, strict
# liability, or tort (including negligence or otherwise) arising in
# any way out of the use of this software, even if advised of the
# possibility of such damage.
#
#
# == Links
#
# John Gruber:: http://daringfireball.net
# SmartyPants:: http://daringfireball.net/projects/smartypants
#
# Chad Miller:: http://web.chad.org
#
# Christian Neukirchen:: http://kronavita.de/chris
#
class RubyPants < String
# Create a new RubyPants instance with the text in +string+.
#
# Allowed elements in the options array:
#
# 0 :: do nothing
# 1 :: enable all, using only em-dash shortcuts
# 2 :: enable all, using old school en- and em-dash shortcuts (*default*)
# 3 :: enable all, using inverted old school en and em-dash shortcuts
# -1 :: stupefy (translate HTML entities to their ASCII-counterparts)
#
# If you don't like any of these defaults, you can pass symbols to change
# RubyPants' behavior:
#
# <tt>:quotes</tt> :: quotes
# <tt>:backticks</tt> :: backtick quotes (``double'' only)
# <tt>:allbackticks</tt> :: backtick quotes (``double'' and `single')
# <tt>:dashes</tt> :: dashes
# <tt>:oldschool</tt> :: old school dashes
# <tt>:inverted</tt> :: inverted old school dashes
# <tt>:ellipses</tt> :: ellipses
# <tt>:convertquotes</tt> :: convert <tt>&quot;</tt> entities to
# <tt>"</tt> for Dreamweaver users
# <tt>:stupefy</tt> :: translate RubyPants HTML entities
# to their ASCII counterparts.
#
def initialize(string, options=[2])
super string
@options = [*options]
end
# Apply SmartyPants transformations.
def to_html
do_quotes = do_backticks = do_dashes = do_ellipses = do_stupify = nil
convert_quotes = false
if @options.include? 0
# Do nothing.
return self
elsif @options.include? 1
# Do everything, turn all options on.
do_quotes = do_backticks = do_ellipses = true
do_dashes = :normal
elsif @options.include? 2
# Do everything, turn all options on, use old school dash shorthand.
do_quotes = do_backticks = do_ellipses = true
do_dashes = :oldschool
elsif @options.include? 3
# Do everything, turn all options on, use inverted old school
# dash shorthand.
do_quotes = do_backticks = do_ellipses = true
do_dashes = :inverted
elsif @options.include?(-1)
do_stupefy = true
else
do_quotes = @options.include? :quotes
do_backticks = @options.include? :backticks
do_backticks = :both if @options.include? :allbackticks
do_dashes = :normal if @options.include? :dashes
do_dashes = :oldschool if @options.include? :oldschool
do_dashes = :inverted if @options.include? :inverted
do_ellipses = @options.include? :ellipses
convert_quotes = @options.include? :convertquotes
do_stupefy = @options.include? :stupefy
end
# Parse the HTML
tokens = tokenize
# Keep track of when we're inside <pre> or <code> tags.
in_pre = false
# Here is the result stored in.
result = ""
# This is a cheat, used to get some context for one-character
# tokens that consist of just a quote char. What we do is remember
# the last character of the previous text token, to use as context
# to curl single- character quote tokens correctly.
prev_token_last_char = nil
tokens.each { |token|
if token.first == :tag
result << token[1]
if token[1] =~ %r!<(/?)(?:pre|code|kbd|script|math)[\s>]!
in_pre = ($1 != "/") # Opening or closing tag?
end
else
t = token[1]
# Remember last char of this token before processing.
last_char = t[-1].chr
unless in_pre
t = process_escapes t
t.gsub!(/&quot;/, '"') if convert_quotes
if do_dashes
t = educate_dashes t if do_dashes == :normal
t = educate_dashes_oldschool t if do_dashes == :oldschool
t = educate_dashes_inverted t if do_dashes == :inverted
end
t = educate_ellipses t if do_ellipses
# Note: backticks need to be processed before quotes.
if do_backticks
t = educate_backticks t
t = educate_single_backticks t if do_backticks == :both
end
if do_quotes
if t == "'"
# Special case: single-character ' token
if prev_token_last_char =~ /\S/
t = "&#8217;"
else
t = "&#8216;"
end
elsif t == '"'
# Special case: single-character " token
if prev_token_last_char =~ /\S/
t = "&#8221;"
else
t = "&#8220;"
end
else
# Normal case:
t = educate_quotes t
end
end
t = stupefy_entities t if do_stupefy
end
prev_token_last_char = last_char
result << t
end
}
# Done
result
end
protected
# Return the string, with after processing the following backslash
# escape sequences. This is useful if you want to force a "dumb" quote
# or other character to appear.
#
# Escaped are:
# \\ \" \' \. \- \`
#
def process_escapes(str)
str.gsub('\\\\', '&#92;').
gsub('\"', '&#34;').
gsub("\\\'", '&#39;').
gsub('\.', '&#46;').
gsub('\-', '&#45;').
gsub('\`', '&#96;')
end
# The string, with each instance of "<tt>--</tt>" translated to an
# em-dash HTML entity.
#
def educate_dashes(str)
str.gsub(/--/, '&#8212;')
end
# The string, with each instance of "<tt>--</tt>" translated to an
# en-dash HTML entity, and each "<tt>---</tt>" translated to an
# em-dash HTML entity.
#
def educate_dashes_oldschool(str)
str.gsub(/---/, '&#8212;').gsub(/--/, '&#8211;')
end
# Return the string, with each instance of "<tt>--</tt>" translated
# to an em-dash HTML entity, and each "<tt>---</tt>" translated to
# an en-dash HTML entity. Two reasons why: First, unlike the en- and
# em-dash syntax supported by +educate_dashes_oldschool+, it's
# compatible with existing entries written before SmartyPants 1.1,
# back when "<tt>--</tt>" was only used for em-dashes. Second,
# em-dashes are more common than en-dashes, and so it sort of makes
# sense that the shortcut should be shorter to type. (Thanks to
# Aaron Swartz for the idea.)
#
def educate_dashes_inverted(str)
str.gsub(/---/, '&#8211;').gsub(/--/, '&#8212;')
end
# Return the string, with each instance of "<tt>...</tt>" translated
# to an ellipsis HTML entity. Also converts the case where there are
# spaces between the dots.
#
def educate_ellipses(str)
str.gsub('...', '&#8230;').gsub('. . .', '&#8230;')
end
# Return the string, with "<tt>``backticks''</tt>"-style single quotes
# translated into HTML curly quote entities.
#
def educate_backticks(str)
str.gsub("``", '&#8220;').gsub("''", '&#8221;')
end
# Return the string, with "<tt>`backticks'</tt>"-style single quotes
# translated into HTML curly quote entities.
#
def educate_single_backticks(str)
str.gsub("`", '&#8216;').gsub("'", '&#8217;')
end
# Return the string, with "educated" curly quote HTML entities.
#
def educate_quotes(str)
punct_class = '[!"#\$\%\'()*+,\-.\/:;<=>?\@\[\\\\\]\^_`{|}~]'
str = str.dup
# Special case if the very first character is a quote followed by
# punctuation at a non-word-break. Close the quotes by brute
# force:
str.gsub!(/^'(?=#{punct_class}\B)/, '&#8217;')
str.gsub!(/^"(?=#{punct_class}\B)/, '&#8221;')
# Special case for double sets of quotes, e.g.:
# <p>He said, "'Quoted' words in a larger quote."</p>
str.gsub!(/"'(?=\w)/, '&#8220;&#8216;')
str.gsub!(/'"(?=\w)/, '&#8216;&#8220;')
# Special case for decade abbreviations (the '80s):
str.gsub!(/'(?=\d\ds)/, '&#8217;')
close_class = %![^\ \t\r\n\\[\{\(\-]!
dec_dashes = '&#8211;|&#8212;'
# Get most opening single quotes:
str.gsub!(/(\s|&nbsp;|--|&[mn]dash;|#{dec_dashes}|&#x201[34];)'(?=\w)/,
'\1&#8216;')
# Single closing quotes:
str.gsub!(/(#{close_class})'/, '\1&#8217;')
str.gsub!(/'(\s|s\b|$)/, '&#8217;\1')
# Any remaining single quotes should be opening ones:
str.gsub!(/'/, '&#8216;')
# Get most opening double quotes:
str.gsub!(/(\s|&nbsp;|--|&[mn]dash;|#{dec_dashes}|&#x201[34];)"(?=\w)/,
'\1&#8220;')
# Double closing quotes:
str.gsub!(/(#{close_class})"/, '\1&#8221;')
str.gsub!(/"(\s|s\b|$)/, '&#8221;\1')
# Any remaining quotes should be opening ones:
str.gsub!(/"/, '&#8220;')
str
end
# Return the string, with each RubyPants HTML entity translated to
# its ASCII counterpart.
#
# Note: This is not reversible (but exactly the same as in SmartyPants)
#
def stupefy_entities(str)
str.
gsub(/&#8211;/, '-'). # en-dash
gsub(/&#8212;/, '--'). # em-dash
gsub(/&#8216;/, "'"). # open single quote
gsub(/&#8217;/, "'"). # close single quote
gsub(/&#8220;/, '"'). # open double quote
gsub(/&#8221;/, '"'). # close double quote
gsub(/&#8230;/, '...') # ellipsis
end
# Return an array of the tokens comprising the string. Each token is
# either a tag (possibly with nested, tags contained therein, such
# as <tt><a href="<MTFoo>"></tt>, or a run of text between
# tags. Each element of the array is a two-element array; the first
# is either :tag or :text; the second is the actual value.
#
# Based on the <tt>_tokenize()</tt> subroutine from Brad Choate's
# MTRegex plugin. <http://www.bradchoate.com/past/mtregex.php>
#
# This is actually the easier variant using tag_soup, as used by
# Chad Miller in the Python port of SmartyPants.
#
def tokenize
tag_soup = /([^<]*)(<[^>]*>)/
tokens = []
prev_end = 0
scan(tag_soup) {
tokens << [:text, $1] if $1 != ""
tokens << [:tag, $2]
prev_end = $~.end(0)
}
if prev_end < size
tokens << [:text, self[prev_end..-1]]
end
tokens
end
end

View File

@ -11,8 +11,8 @@ class String
# capitalize first and last words # capitalize first and last words
x.first.to_s.smart_capitalize! x.first.to_s.smart_capitalize!
x.last.to_s.smart_capitalize! x.last.to_s.smart_capitalize!
# small words after colons or periods are capitalized # small words are capitalized after colon, period, exclamation mark, question mark
x.join(" ").gsub(/(:|\.)\s?(\W*#{small_words.join("|")}\W*)\s/) { "#{$1} #{$2.smart_capitalize} " } x.join(" ").gsub(/(:|\.|!|\?)\s?(\W*#{small_words.join("|")}\W*)\s/) { "#{$1} #{$2.smart_capitalize} " }
end end
def titlecase! def titlecase!