packages feed

panda 2008.10.18 → 2008.10.19

raw patch · 17 files changed

+210/−221 lines, 17 files

Files

Panda/Config/Global.hs view
@@ -16,7 +16,7 @@  db_id       = "db" flat_id     = "."-blog_id     = "blog"+post_id     = "blog" config_id   = "config" tag_id      = "tag" comment_id  = "comment"@@ -28,7 +28,7 @@  config_uri  = flat_uri / config_id / "site.txt" sidebar_uri = flat_uri / config_id / sidebar_id-blog_uri    = flat_uri / blog_id+post_uri    = flat_uri / post_id tag_uri     = flat_uri / tag_id comment_uri = flat_uri / comment_id theme_uri   = flat_uri / config_id / theme_id@@ -76,3 +76,13 @@ theme = if user_theme_uri.doesFileExist.purify   then user_config' user_theme_uri .(("name", user_theme_name) : ) .to_theme   else default_theme+  +-- custom+as_l s                   = "[" ++ s ++ "]"+post_date_format         = config_for' "post_date_format" "%y-%m-%d %T"+comment_date_format      = config_for' "comment_date_format" "%y-%m-%d %T"+url_date_format          = config_for' "url_date_format" "%y-%m-%d"+url_date_matcher         = config_for' "url_date_matcher" "\\d{2}-\\d{2}-\\d{2}"+url_title_subs           = config_for' "url_title_subs" "" .as_l .read :: [(String, String)]+url_date_title_seperator = config_for' "url_date_title_seperator" " "+
Panda/Controller/Application.hs view
@@ -12,7 +12,7 @@ import qualified Data.Map as Map  -- env-import Panda.Helper.Env hiding (tag)+import Panda.Helper.Env hiding (tag, uri) import Panda.Helper.StateHelper import Prelude hiding ((.), (/), (^), id) import qualified Panda.Type.State as S@@ -33,10 +33,19 @@ import qualified Panda.View.Control.Search as SearchV import qualified Panda.View.Widget.RSS as RSSV -per_page = G.per_page-root     = G.root+-- helpers+-- liftIO: move to an (u)pper level monad+u = liftIO+blog_regex = G.url_date_matcher -blog_regex = "\\d{2}-\\d{2}-\\d{2}"+init_post tags x = do+  x.Tag.fill_tag tags.Comment.fill_comment_size.u++tag_list = Tag.list.u+++-- main controller+ pages =    [ ("$"                  ,index             )   , ("(\\?.+)?$"          ,index             )@@ -47,88 +56,80 @@   , ("tag/."              ,tag               )   , ("search"             ,search            )   , ("comment/create"     ,comment_create    )-  ] .map_fst ((root /) >>> ("^" ++))+  ] .map_fst ((G.root /) >>> ("^" ++))   ++-  [ ("^" ++ root ++ "$"          ,index      )]-+  [ ("^" ++ G.root ++ "$"          ,index      )]   index = do-  blogs <- Post.list.liftIO-  p <- paginate per_page $ blogs.length-  tags <- Tag.list.liftIO+  posts <- Post.list.u+  p <- paginate posts+  tags <- tag_list   let nav = if p.Pager.current == 1 then home_nav else no_navigation-  let state = S.empty { S.uid = G.blog_id, S.pager = p, S.tags = tags, S.nav_location = nav }+  let state = S.empty { S.uid = G.post_id, S.pager = p, S.tags = tags, S.nav_location = nav }   -  blogs.map (Tag.fill_tag tags).mapM ( Comment.fill_comment_size >>> liftIO ) -    ^ PostV.list state >>= output_html-+  posts.mapM (init_post tags) ^ PostV.list state >>= output_html  index_feed = do-  blogs <- Post.list.liftIO-  blogs.RSSV.rss "" "" .output+  posts <- Post.list.u+  posts.RSSV.rss "" "" .output  blog = do-  id <- uri ^ ( "blog" / )-  blog <- Post.get id .liftIO-  comments <- Comment.list_for id .liftIO-  tags <- Tag.list.liftIO+  id <- uri ^ Post.uri_to_id+  blog <- Post.get id .u+  comments <- Comment.list_for id .u+  tags <- tag_list      let state = S.empty { S.uid = id, S.tags = tags, S.resource_title = blog.resource_title }-  blog.Tag.fill_tag tags .Comment.fill_comment_size .liftIO-    ^ PostV.view state comments >>= output_html+  blog.(init_post tags) ^ PostV.view state comments >>= output_html  static = do   id <- uri-  static_page <- Static.get id .liftIO-  tags <- Tag.list.liftIO+  static_page <- Static.get id .u+  tags <- tag_list      let nav_id = static_page.Static.uid.id_to_resource   let nav   = if nav_id.belongs_to G.navigation then nav_id else no_navigation   let state = S.empty { S.uid = id, S.tags = tags, S.nav_location = nav, S.resource_title = static_page.resource_title }   StaticV.view state static_page .output_html - tag = do   id <- uri-  tags <- Tag.list.liftIO+  tags <- tag_list   let tag_name = Tag.get_name id   case Tag.tag_map' tags .Map.lookup tag_name of-    Nothing -> "page not found" .output-    Just blog_set -> do-      blogs <- blog_set.to_list.rsort.mapM (Post.get) .liftIO ^ map (Tag.fill_tag tags)-      p <- paginate per_page $ blogs.length+    Nothing -> not_found+    Just post_set -> do+      posts <- post_set.to_list.rsort.mapM (Post.get) .u >>= mapM (init_post tags)+      p <- paginate posts       let state = S.empty { S.uid = id, S.pager = p, S.tags = tags, S.resource_title = tag_name.Tag.resource_title_from_name }-      blogs.TagV.view state.output_html-+      posts.TagV.view state.output_html  tag_feed = do   id <- uri ^ (split "/" >>> init >>> join "/")-  tags <- Tag.list.liftIO+  tags <- tag_list   let tag_name = Tag.get_name id   case Tag.tag_map' tags .Map.lookup tag_name of-    Nothing -> "page not found" .output-    Just blog_set -> do-      blogs <- blog_set.to_list.rsort.mapM (Post.get) .liftIO ^ map (Tag.fill_tag tags)-      blogs.RSSV.rss "tag" tag_name.output+    Nothing -> not_found+    Just post_set -> do+      posts <- post_set.to_list.rsort.mapM (Post.get) .u ^ map (Tag.fill_tag tags)+      posts.RSSV.rss G.tag_id tag_name.output  search = do   s <- param_with_default "s" ""-  blogs <- Post.search s .liftIO+  posts <- Post.search s .u -  p <- paginate per_page $ blogs.length-  tags <- Tag.list.liftIO+  p <- paginate posts+  tags <- tag_list   query <- uri   let state = S.empty { S.uid = query, S.pager = p, S.tags = tags, S.resource_title = query } -  blogs.map (Tag.fill_tag tags) .SearchV.view state s.output_html--+  posts.mapM (init_post tags) >>= \x -> x.SearchV.view state s.output_html  comment_create = do-  post_id <- input_with_default "post_id" (G.blog_id / "nothing")-  exists <- (G.flat_uri / post_id.to_utf8) .doesFileExist .liftIO-  let valid_path = equalFilePath G.blog_id (takeDirectory post_id)+  post_id <- input_with_default "post_id" (G.post_id / "nothing")+  exists <- (G.flat_uri / post_id.to_utf8) .doesFileExist .u+  let valid_path = equalFilePath G.post_id (takeDirectory post_id)   checked <- check_create      if [checked, valid_path, exists].and@@ -136,15 +137,15 @@       inputs >>= (Comment.create >>> liftIO)     else       return ()-  redirect $ (G.root / post_id.id_to_resource.to_utf8).urlEncode+  redirect $ (post_id.Post.id_to_uri.to_utf8).urlEncode   -- create helper, should be refactored to some common aspect check_create =   [ validate "author"  ( length >>> (> 0))-  , validate "url"     (const True)+  , validate "author_link"     (const True)   , validate "comment" ( length >>> (> 0))-  , validate "human-hack" (is "10")+  , validate "human_hack" (is "10")   ] .sequence ^ and  
Panda/Helper/Env.hs view
@@ -20,7 +20,7 @@   , module Data.Foldable ) where   -import MPS hiding (base, col, sub)+import MPS hiding (base, col, sub, date) import Control.Monad hiding (join) import Control.Arrow ((>>>), (&&&), (***)) import Data.List hiding (find)
Panda/Helper/Helper.hs view
@@ -13,15 +13,15 @@ import Text.XHtml.Strict import Control.Monad hiding (join) import Data.Maybe- import Panda.Type.Pager as Pager hiding (empty)-import MPS+import MPS hiding (date) import Prelude hiding ((.), (/), (^), id) import Char import Data.List import qualified Data.List as L  import Panda.Type.Reader import System.FilePath.Posix hiding ((<.>))+import System.Time  (/) = (</>) infixl 5 /@@ -35,14 +35,14 @@ -- controller raw_uri = requestURI ^ (uriPath >>> urlDecode >>> tail >>> remove_trailing_slash >>> from_utf8 ) remove_trailing_slash s = if s.last.is '/' then s.init else s-+   params = do   query_string <- requestURI ^ uriQuery    case query_string of     '?':s -> s.formDecode .map_snd unescape_unicode_xml.return     otherwise -> return [] -inputs = getInputs ^ trace' ^ map_snd (strip >>> gsub "\r\n" "\n" >>> unescape_unicode_xml) ^ trace'+inputs = getInputs ^ map_snd (strip >>> unescape_unicode_xml)  param_with_default s d = params ^ (lookup s >>> fromMaybe d ) input_with_default s d = inputs ^ (lookup s >>> fromMaybe d )@@ -53,7 +53,7 @@ just_param s = get_param s ^ fromJust just_input s = get_input s ^ fromJust -paginate length total = liftM5 ( Pager length ) current has_next has_previous next previous where+full_paginate length total = liftM5 ( Pager length ) current has_next has_previous next previous where   current      = param_with_default "page" "1" ^ read   has_next     = current ^ ( (* length) >>> (< total.fromIntegral) )   has_previous = current ^ (> 1)@@ -86,15 +86,11 @@  output_html      = output ... renderHtml --ie s = -  [ "<!-- [if IE]>"-  , s.show-  , "<![endif]-->"-  ] .join' .primHtml+spaced_url = gsub "/" " / "  -- config parse_config s = s.split "," .map strip .reject null+not_found = getVar "REQUEST_URI" >>= (fromMaybe "" >>> outputNotFound)   -- class@@ -110,4 +106,8 @@ class Markable a where     markup :: a -> Html     -spaced_url = gsub "/" " / "+class Datable a where+    date :: a -> CalendarTime++class Addressable a where+    uri :: a -> String
Panda/Helper/StateHelper.hs view
@@ -8,6 +8,7 @@ import qualified Panda.Config.Global as G import Prelude hiding ((.), (/), (^), id) + -- G.root = /blog -- raw_uri = blog/x -- full_uri = /blog/x@@ -18,3 +19,19 @@  uri = raw_uri ^ remove_root -- uri = raw_uri+++-- global+parse_date format s = case maybe_d of+  Nothing -> Nothing+  Just d -> Just $ if d.ctYear < 1910 then d {ctYear = d.ctYear + 100} else d +  where+    maybe_d = parseCalendarTime defaultTimeLocale format s+  +format_date          = formatCalendarTime defaultTimeLocale+default_date         = parse_date "%Y-%m-%d %T" "2000-1-1 00:00:00".fromJust+parse_post_date      = parse_date G.post_date_format+default_parse_date s = s.parse_post_date .fromMaybe default_date++-- controller+paginate = length >>> full_paginate (G.per_page)
Panda/Model/Comment.hs view
@@ -6,14 +6,15 @@ import qualified Panda.Config.Global as G import Panda.Type.Reader import qualified Panda.Model.Post as Post-import System.Time import System.Directory+import Panda.Helper.StateHelper  data Comment = Comment   { uid    :: String     -- comment/08-09-04 blog title   , author :: String-  , url    :: String   , body   :: String+  , author_email :: String+  , author_link    :: String   }   deriving (Show, Eq) @@ -23,6 +24,10 @@ instance Markable Comment where   markup x  = render_to_html Markdown (x.body) +instance Datable Comment where+  date x  = x.uid.split "/".last.default_parse_date++-- CRUD list_for post_id = do   has_comments <- doesDirectoryExist d   if has_comments@@ -32,37 +37,36 @@     d = (G.comment_uri / r)     r = post_id.id_to_resource.to_utf8 -from_post_id x = Comment (post_id_to_uid x) "" "" ""--post_id_to_uid x = G.flat_uri / G.comment_id / x.split "/" .last-uid_to_post_id x = G.blog_id / x.split "/" .last- get id = do   xs <- (G.flat_uri / id.to_utf8) .readFile ^ lines   let author = xs.first-  let url = xs.drop 2.first+  let author_email = ""+  let author_link = xs.drop 2.first   let body = xs.drop 4.unlines-  return $ Comment id author url body+  return $ Comment id author body author_email author_link  create h = do-  let author  = at "author"-  let url     = at "url"-  let body    = at "comment"-  let post_id = at "post_id"+  let author      = at "author"+  let author_link = at "author_link"+  -- let author_email = at "author_email"+  let body        = at "comment"+  let post_id     = at "post_id" -  timestamp <- ( getClockTime >>= toCalendarTime ) ^ format_date+  timestamp <- ( getClockTime >>= toCalendarTime ) ^ format_date G.comment_date_format   let comment_path = post_id_to_uid post_id .to_utf8   createDirectoryIfMissing True comment_path      let uid = comment_path / timestamp-  let content = [author, url, body].join "\n\n"+  let content = [author, author_link, body].join "\n\n"   writeFile uid content      where at s = h.lookup s .fromJust -format_date x = formatCalendarTime defaultTimeLocale "%y-%m-%d %T" x-parse_date = parseCalendarTime defaultTimeLocale "%Y-%m-%d %T"-date x     = x.uid.split "/".last.("20"++).parse_date.fromMaybe (parse_date "2000-1-1 00:00:00".fromJust)+-- extra+from_post_id x = Comment (post_id_to_uid x) "" "" "" ""++post_id_to_uid x = G.flat_uri / G.comment_id / x.split "/" .last+uid_to_post_id x = G.post_id / x.split "/" .last  fill_comment_size x = do   size <- x.Post.uid.list_for ^ length 
Panda/Model/Post.hs view
@@ -5,10 +5,13 @@ module Panda.Model.Post where  -- env-import Panda.Helper.Env hiding (match, title, body)+import Panda.Helper.Env hiding (match, title, body, date)+import qualified MPS as MPS import Prelude hiding ((.), (/), (^), id, readFile) import qualified Panda.Config.Global as G import Panda.Type.Reader+import Panda.Helper.StateHelper+import Panda.Helper.Helper (date)  data Post = Post    { uid :: String     -- blog/08-09-04 blog title@@ -25,21 +28,37 @@  instance Markable Post where   markup x  = render_to_html (x.reader) (x.body)-    -list = ls G.blog_uri ^ map from_utf8 ^ rsort ^ map (G.blog_id /) >>= mapM get+  +instance Datable Post where+  date = uid >>> get_date ++instance Addressable Post where+  uri = uid >>> id_to_uri++-- CRUD+list = ls G.post_uri ^ map from_utf8 ^ rsort ^ map (G.post_id /) >>= mapM get+ get id        = liftM4 (Post id (get_title id) ) (get_body id) (return []) (return 0) (return $ get_reader id)+get_extension = takeExtension get_title id  = id.words.tail.unwords.dropExtension get_body id   = (G.flat_uri / id.to_utf8) .readFile get_reader id = id.take_extension.guess_reader.fromMaybe G.default_reader---parse_date = parseCalendarTime defaultTimeLocale "%Y-%m-%d"-date x     = x.uid.words.first.split "/".last.("20"++).parse_date.fromMaybe (parse_date "2000-1-1".fromJust)-+get_date id   = id.words.first.split "/".last.default_parse_date  match s x = [title, body] .map (send_to x >>> lower >>> isInfixOf (s.lower)) .or search "" = return [] search s  = list ^ filter (match s) +-- extra+id_to_uri id = G.root / ( pretty_date ++ G.url_date_title_seperator ++ formatted_title ++ ext ) where+  formatted_title = G.url_title_subs.map (\(a,b) -> gsub a b).inject (id.get_title) apply+  pretty_date     = id.get_date.format_date G.url_date_format+  ext = id.get_extension++uri_to_id s = G.post_id / (d ++ " " ++ t) where+  (raw_d, (prefix, title_with_sep)) = s.MPS.match G.url_date_matcher.fromJust.fst+  raw_t = title_with_sep.drop (G.url_date_title_seperator.length)+  t = G.url_title_subs.map (\(a,b) -> gsub b a) .inject raw_t apply+  d = raw_d.parse_date G.url_date_format .fromJust.format_date G.post_date_format 
Panda/Model/Static.hs view
@@ -19,6 +19,7 @@ instance Markable Static where   markup x = render_to_html (x.reader) (x.body)   +-- CRUD get id        = liftM2 (Static id) (get_body id) (return $ get_reader id) get_title     = id_to_resource >>> dropExtension get_body id   = (G.flat_uri / id.to_utf8) .readFile
Panda/Model/Tag.hs view
@@ -19,15 +19,13 @@ instance Resource Tag where   resource_title = uid >>> spaced_url -sorted xs    = xs.sortBy(compare_by (resources >>> S.size)).reverse-name_to_id x = G.tag_id / x-+-- CRUD list             = ls G.tag_uri ^ map from_utf8 ^ map (G.tag_id /) >>= mapM get  get id           = get_resources id ^ Tag id (get_name id) get_name id      = id.split "/" .tail.join' get_resources id = (G.flat_uri / id.to_utf8) .readFile-  ^ filter_comment ^ lines ^ map (G.blog_id / ) ^ to_set+  ^ filter_comment ^ lines ^ map (G.post_id / ) ^ to_set  resource_title_from_name = name_to_id >>> spaced_url tag_map' xs       = xs . map (name &&& resources) . to_h@@ -35,3 +33,7 @@  for_resource xs x = xs.select (resources >>> has x) .map name fill_tag xs x     = x { Post.tags = for_resource xs (x.Post.uid) }++-- extra+sorted xs    = xs.sortBy(compare_by (resources >>> S.size)).reverse+name_to_id x = G.tag_id / x
Panda/View/Atom/Comment.hs view
@@ -1,8 +1,9 @@ module Panda.View.Atom.Comment where   -import Panda.Helper.Env hiding (title, date)+import Panda.Helper.Env hiding (title) import Prelude hiding ((.), (/), (^), id, span) import qualified Panda.Config.Global as G+import Panda.Helper.StateHelper  -- view import qualified Panda.View.Atom.Tag as Tag@@ -21,9 +22,9 @@   , p ! [theclass "small"] << comment_date x   , x.markup   ]-  where a = if x.url.null then x.author.toHtml else toHtml $ hotlink (x.url) << x.author+  where a = if x.author_link.null then x.author.toHtml else toHtml $ hotlink (x.author_link) << x.author -comment_date x = toHtml $ x.date.formatCalendarTime defaultTimeLocale "%b %e, %Y at %I:%M %p"+comment_date x = toHtml $ x.date.format_date "%b %e, %Y at %I:%M %p"   -- form@@ -34,11 +35,12 @@   [ h2 ! [id "respond"] << "Leave a Response"   , gui (G.root / "comment/create") ! [id "commentform"] <<     [ field "author" 22 1 "Name (required)"-    , field "url" 22 2 "Website"-    , field "human-hack" 22 3 "What is 5 + 5 ?"+    -- , field "author_email" 22 2 "Email"+    , field "author_link" 22 3 "Website"+    , field "human_hack" 22 4 "What is 5 + 5 ?"     , p << hidden "post_id" (x.uid.uid_to_post_id)-    , p << textarea ! [name "comment", id "comment", cols "10", rows "20", strAttr "tabindex" "4"] << ""-    , p << submit "submit" "Submit Comment" ! [strAttr "tabindex" "5"]+    , p << textarea ! [name "comment", id "comment", cols "10", rows "20", strAttr "tabindex" "5"] << ""+    , p << submit "submit" "Submit Comment" ! [strAttr "tabindex" "6"]     ]   ] 
Panda/View/Atom/Post.hs view
@@ -1,8 +1,9 @@ module Panda.View.Atom.Post where   -import Panda.Helper.Env hiding (name, title, date)+import Panda.Helper.Env hiding (name, title) import Prelude hiding ((.), (/), (^), id, span) import qualified Panda.Config.Global as G+import Panda.Helper.StateHelper hiding (uri)  -- view import qualified Panda.View.Atom.Tag as Tag@@ -18,14 +19,13 @@ entry x = div_class "post" << [entry_title, entry_mark, entry_body] where   entry_title = x.title_link   entry_body  = div_class "entry" << x.markup-  entry_mark  = p ! [theclass "small"] << [ blog_date, blog_tags, blog_comments ].map (send_to x)+  entry_mark  = p ! [theclass "small"] << [ post_date, post_tags, post_comments ].map (send_to x) -title_link x              = h2 << hotlink (entry_uri x) << x.title-entry_uri x               = G.root / ( uid x .id_to_resource )-blog_date x               = toHtml $ x.date.formatCalendarTime defaultTimeLocale "%b %e, %Y"+title_link x              = h2 << hotlink (x.uri) << x.title+post_date x               = toHtml $ x.date.format_date "%b %e, %Y" -blog_tags x | x.tags.null = toHtml ""-blog_tags x = " | " +++ "Published in " +++ x.tags.map Tag.tag_link .intersperse (", ".toHtml)+post_tags x | x.tags.null = toHtml ""+post_tags x = " | " +++ "Published in " +++ x.tags.map Tag.tag_link .intersperse (", ".toHtml) -blog_comments x | x.comment_size.is 0 = toHtml ""-blog_comments x = " | " +++ hotlink ( entry_uri x / "#comments") << (x.comment_size.show ++ " Comments")+post_comments x | x.comment_size.is 0 = toHtml ""+post_comments x = " | " +++ hotlink ( x.uri / "#comments") << (x.comment_size.show ++ " Comments")
Panda/View/Atom/Tag.hs view
@@ -8,5 +8,5 @@ instance DataRenderer Tag where   render_data = name >>> tag_link -tag_link s = toHtml $ hotlink (G.root / "tag" / s ) << s +tag_link s = toHtml $ hotlink (G.root / G.tag_id / s ) << s  
Panda/View/Widget/Helper.hs view
@@ -11,7 +11,7 @@   if tagged    then     let tag_name = Tag.get_name uid in-      link ( "tag" / tag_name) tag_name+      link ( G.tag_id / tag_name) tag_name   else link "" "Home"   where     uid = State.uid state
Panda/View/Widget/RSS.hs view
@@ -4,16 +4,13 @@ import Panda.Helper.Env -- hiding (Language) import Prelude hiding ((.), (/), (^), id) import qualified Panda.Config.Global as G-import Panda.Type.Pager as Pager+import qualified Panda.Type.Pager as Pager import Text.RSS import qualified Text.RSS as RSS  -- model import Panda.Model.Post as Post --entry_uri x = Post.uid x.split "/".tail.join "/"- -- RSS channel_rss_template = [ RSS.Language "en-us" ] @@ -22,16 +19,16 @@   , Description $ x.markup.show   , Author $ G.author_email   , Link $ x.item_uri-  , PubDate $ x.Post.date +  , PubDate $ x.date   ] -item_uri x = nullURI { uriScheme = "http://", uriPath = host_link (x.entry_uri) }+item_uri x = nullURI { uriScheme = "http://", uriPath = host_link (x.uri) } rss_uri x  = nullURI { uriScheme = "http://", uriPath = host_link x }  rss categary s xs = RSS title  (rss_uri link) title channel_rss_template (xs.take 20 .map item_rss_template)   .rssToXML.showXML   where     link = categary / s-    title = G.blog_title ++ " / " ++ s+    title = if s.empty then G.blog_title else G.blog_title ++ " / " ++ s  host_link s = G.host_name ++ (G.root / s)
changelog.markdown view
@@ -1,3 +1,11 @@+2008.10.19+-----------++### Feature++* Pretty URLs+* Configurable date format for plain text+ 2008.10.18 ----------- 
panda.cabal view
@@ -1,5 +1,5 @@ Name:                 panda-Version:              2008.10.18+Version:              2008.10.19 Build-type:           Simple Synopsis:             A simple static blog engine Description:          A simple static blog engine
readme.markdown view
@@ -1,9 +1,9 @@-Panda - simple blogging engine-==============================+## Panda: a simple blog engine in Haskell -Sample file db structure--------------------------+### Features +#### simple file structure+     db     |---- blog     |     |---- 08-09-01 first post@@ -15,78 +15,23 @@     |     |---- static           |---- About-   -Why-------* Blogs are stored locally-* Write in Markdown, HTML, reStructuredText or even LaTeX format ( Math equations won't render yet )-* Remote server is a mirror of local repository-* No server side API for publishing / editing, just use Darcs, Git or any SCM-* No need to find external hosting of your videos, images or sound. Put them in public folder ( still managed by SCM if you wish )-* Easy to use, just create a file and the engine takes care of the rest-* Extensible through embedding any HTML snippets-* Use `custom.css` for theming--Quick Demo--------------It powers [my blog](http://jinjing.blog.easymic.com) ;)--Note: the demo might run on an experimental branch, so differences from the cabal version could occur.-        -Install-----------### install [lighttpd (version 1.4.19)](http://www.lighttpd.net/ )--Note: 1.4.20 is bugged for reason still unknown yet.---### install panda--    cabal install panda--### bootstrap--    # panda is a kibro project-    kibro new myblog-    cd myblog-    -    # get a template to start-    git clone git://github.com/nfjinjing/panda-template.git db-    sh db/extra/scripts/bootstrap.sh-    -### run--	kibro start--It should be running on `http://127.0.0.1:3000` now.--Since Panda is based on Kibro, it helps to read how [Kibro](http://chrisdone.com/blog/2008/08/06/kibro-haskell-lighttpd-and-fastcgi/) works.--Goodies-----------Install `Ruby` / `Rake`, then `rake -T` to see a list of helper commands.--Config----------Edit `db/config/site.txt`+#### simple config file -    blog_title   = My shiny blog-    host_name    = yourhost.com-    author_email = your_mail@yourhost.com+    blog_title    = C大调+    blog_subtitle = 野猫不吃薯片+    host_name     = jinjing.easymic.com+    author_email  = nfjinjing@gmail.com -Restart required. (hint: `rake r`)+    navigation    = About+    per_page      = 7+    sidebar       = Blogroll.md+    favicon       = panda_icon.png  -Theme---------+#### clear separation of code and data -Example blueprint theme in `db/config/theme/blueprint.txt`+theme structure through, for example, `db/config/theme/blueprint.txt`:      container  = container     header     = column span-12 first@@ -94,44 +39,27 @@     main       = column span-9 first     sidebar    = column span-3 last     footer     = footer-+         css        = screen, blueprint-wp, custom-    js         = jquery-1.2.6.min, jquery.getUrlParam, jquery.highlight-2, custom-+    js         = jquery-1.2.6.min, custom -These are custom class names for each element block.+theme styling inside, for example, `db/public/theme/blueprint`, where all theme specific resources, e.g. `css/js/images`, are hosted. -note: `css` and `js` are required to be placed under `db/public/theme/your_theme_name/css` and `db/public/theme/your_theme_name/js`, leave out the file extensions in `site.txt`.+#### standard web technology -You can create new themes by adding theme files in `db/config/theme`, and place+extend/customize with pure css and javascript ( note the custom part in css and js from the last section ) -    theme = your_new_theme+#### hacker friendly -inside `site.txt`.+* posts are in plain text+* SCM for publishing / data managing ( not really a feature, but a design goal from the beginning )+* blindly follow the KISS principle from code to UI: be a simple blog, no more no less -Hacking---------+### Try -Another way to get Panda running with source is:+Follow the [install guide](/static/panda/install) -    # clone the source-    git clone git://github.com/nfjinjing/panda.git myblog-    cd myblog-    -    # get a template to start-    git clone git://github.com/nfjinjing/panda-template.git db-    -    # manually adjust path information for lighttpd-    # edit first 6 lines of app/lighttpd/lighttpd.conf to match your path-    -    # manual staging-    ln -s db/public public-    -    # run-    kibro start-    -I'm heavily using the [MPS](http://github.com/nfjinjing/mps/tree/master ) package which introduces things like+### Links -    reject, join, belongs_to, match, gsub-    -and so on.+* [Kibro](http://chrisdone.com/tag/Kibro)+* [Panda on Jinjing's blog](http://jinjing.blog.easymic.com/tag/Panda)