diff --git a/docs/plugin-use.md b/docs/plugin-use.md
index fba2d6e..2337a31 100644
--- a/docs/plugin-use.md
+++ b/docs/plugin-use.md
@@ -39,6 +39,34 @@
**Example**: `(gh-pages :cname t)`
+## SEO and Social Metadata
+
+**Description**: Adds description and keywords metadata for SEO purposes.
+ Adds both Open Graph and Twitter Cards metadata for sharing posts as well.
+ `:twitter` keyword argument sets "site" property of Twitter.
+ Twitter Card specific metadata will be omitted if this property is not set.
+ `:card` keyword argument sets the type of Twitter card created.
+ Possible types are `:summary` and `:image`. Defaults to `:summary`.
+
+ Five *optional* tags for post file header are added.
+
+ `keywords:` comma, seperated, seo, keywords.
+ They will be generated from tags if empty.
+
+ `description:` Description to be used in SEO and
+ Open Graph description tags. If empty, Open Graph description will be generated
+ from content while SEO description metadata will be omitted.
+
+ `image:` either an absolute (`http://www.example.com/image.png`)
+ or a root-relative (`/static/image.png`) image URL.
+
+ `card:` Overrides Twitter Card type defined in plugin activation.
+ Possible values are either `image` or `summary`.
+
+ `creator:` Twitter username of the content creator.
+
+**Example**: `(metadata :twitter "twitter_account" :card :summary)`
+
## Incremental Builds
**Description**: Primarily a performance enhancement. Caches the
diff --git a/plugins/metadata.lisp b/plugins/metadata.lisp
new file mode 100644
index 0000000..efb82b9
--- /dev/null
+++ b/plugins/metadata.lisp
@@ -0,0 +1,81 @@
+(eval-when (:compile-toplevel :load-toplevel)
+ (ql:quickload 's-xml))
+
+(defpackage :coleslaw-metadata
+ (:use :cl :coleslaw)
+ (:import-from :coleslaw
+ #:title
+ #:domain
+ #:tag-name
+ #:page-url
+ #:content-tags
+ #:content-text
+ #:keywords-of
+ #:description-of
+ #:image-of
+ #:card-format
+ #:creator-of)
+ (:import-from :s-xml
+ #:xml-parser-state
+ #:start-parse-xml)
+ (:export #:enable))
+
+(in-package :coleslaw-metadata)
+
+(defparameter *description-length* 200)
+
+(defvar *metadata-header*
+ "
+~@[
+~]~:[~*~*~;~:*
+
+~@[
+~]~]
+
+
+
+~@[
+~]
+")
+
+(defun remove-markup (text)
+ (with-input-from-string (in text)
+ (let* ((state (make-instance 'xml-parser-state
+ :text-hook #'(lambda (string seed) (cons string seed))))
+ (result (start-parse-xml in state)))
+ (apply #'concatenate 'string (nreverse result)))))
+
+(defun shorten-text (text)
+ (if (< *description-length* (length text))
+ (subseq text 0 (- *description-length* 1)) text))
+
+(defun compile-description (text)
+ (shorten-text (remove #\" (remove-markup text))))
+
+(defun root-relative-url-p (url)
+ (eq (elt url 0) #\/))
+
+(defun compile-url (url)
+ (if (root-relative-url-p url)
+ (concatenate 'string (domain *config*) url)
+ url))
+
+(defun compile-metadata (post twitter card)
+ (format nil *metadata-header*
+ (or (keywords-of post)
+ (format nil "~{~A~^, ~}" (mapcar #'tag-name (content-tags post))))
+ (description-of post)
+ twitter
+ (eq (or (card-format post) card) :image)
+ (creator-of post)
+ (title-of post)
+ (title *config*)
+ (concatenate 'string (domain *config*) "/" (namestring (page-url post)))
+ (or (description-of post)
+ (compile-description (content-text post)))
+ (when (image-of post) (compile-url (image-of post)))))
+
+(defun enable (&key twitter card)
+ (flet ((inject-p (x)
+ (when (typep x 'post) (compile-metadata x twitter card))))
+ (add-injection #'inject-p :head)))
diff --git a/src/posts.lisp b/src/posts.lisp
index d8fe29d..71d8100 100644
--- a/src/posts.lisp
+++ b/src/posts.lisp
@@ -3,15 +3,27 @@
(defclass post (content)
((title :initarg :title :reader title-of)
(author :initarg :author :reader author-of)
- (format :initarg :format :reader post-format))
- (:default-initargs :author nil))
+ (format :initarg :format :reader post-format)
+ (keywords :initarg :keywords :reader keywords-of)
+ (description :initarg :description :reader description-of)
+ (image :initarg :image :reader image-of)
+ (card :initarg :card :reader card-format)
+ (creator :initarg :creator :reader creator-of))
+ (:default-initargs
+ :author nil
+ :keywords nil
+ :description nil
+ :image nil
+ :card nil
+ :creator nil))
(defmethod initialize-instance :after ((object post) &key)
- (with-slots (url title author format text) object
+ (with-slots (url title author format card text) object
(setf url (compute-url object (slugify title))
format (make-keyword (string-upcase format))
text (render-text text format)
- author (or author (author *config*)))))
+ author (or author (author *config*))
+ card (if card (make-keyword (string-upcase card))))))
(defmethod render ((object post) &key prev next)
(funcall (theme-fn 'post) (list :config *config*