panda 2008.11.7 → 2008.12.14
raw patch · 19 files changed
+330/−145 lines, 19 filesdep +process
Dependencies added: process
Files
- changelog.markdown +7/−0
- panda.cabal +4/−3
- src/Panda/Config/Global.hs +21/−5
- src/Panda/Controller/Application.hs +30/−23
- src/Panda/Helper/Env.hs +1/−1
- src/Panda/Helper/Helper.hs +81/−21
- src/Panda/Helper/PreludeEnv.hs +11/−3
- src/Panda/Helper/StateHelper.hs +7/−4
- src/Panda/Helper/ThumbHelper.hs +31/−0
- src/Panda/Model/Comment.hs +54/−45
- src/Panda/Model/Post.hs +40/−15
- src/Panda/Model/Static.hs +17/−7
- src/Panda/Model/Tag.hs +10/−6
- src/Panda/Type/Reader.hs +4/−3
- src/Panda/Type/State.hs +8/−6
- src/Panda/Type/Theme.hs +1/−1
- src/Panda/View/Atom/Comment.hs +1/−1
- src/Panda/View/Control/Post.hs +1/−0
- src/Panda/View/Widget/Template.hs +1/−1
changelog.markdown view
@@ -1,3 +1,10 @@+2008.12.14+----------++### Feature++* Photo album plugin+ 2008.11.7 ---------
panda.cabal view
@@ -1,5 +1,5 @@ Name: panda-Version: 2008.11.7+Version: 2008.12.14 Build-type: Simple Synopsis: A simple static blog engine Description: A simple static blog engine@@ -11,11 +11,11 @@ Cabal-version: >= 1.2 category: Web homepage: http://www.haskell.org/haskellwiki/Panda-data-files: readme.markdown, changelog.markdown+data-files: readme.markdown, changelog.markdown library ghc-options: -Wall -fno-warn-missing-signatures -fno-warn-name-shadowing -fno-warn-orphans -fno-warn-type-defaults- build-depends: base, cgi, network, haskell98, old-locale, old-time, directory, filepath, containers, mps >= 2008.11.6, parsedate >= 3000.0.0, rss >= 3000.0.1, xhtml, kibro >= 0.4.2, utf8-string >= 0.3.3, pandoc, parsec >= 2, MissingH, gravatar >= 0.3, data-default >= 0.2, hcheat >= 2008.11.6+ build-depends: base, cgi, network, haskell98, old-locale, old-time, directory, filepath, containers, process, mps >= 2008.11.6, parsedate >= 3000.0.0, rss >= 3000.0.1, xhtml, kibro >= 0.4.2, utf8-string >= 0.3.3, pandoc, parsec >= 2, MissingH, gravatar >= 0.3, data-default >= 0.2, hcheat >= 2008.11.6 hs-source-dirs: src/ exposed-modules: Panda@@ -28,6 +28,7 @@ Panda.Helper.Helper Panda.Helper.PreludeEnv Panda.Helper.StateHelper+ Panda.Helper.ThumbHelper Panda.Model.Comment Panda.Model.Post
src/Panda/Config/Global.hs view
@@ -5,7 +5,7 @@ import Panda.Helper.Helper import Panda.Helper.PreludeEnv -import Control.Arrow ((>>>))+ import Data.Maybe import Data.Default @@ -23,9 +23,17 @@ sidebar_id = "sidebar" theme_id = "theme" config_file_id = "site.txt"+album_id = "album"+image_id = "images"+public_id = "public"+static_id = "static"+topic_id = "forum/post"+thumb_id = "thumb" db_uri = db_id flat_uri = db_uri / flat_id+public_uri = db_uri / public_id+image_uri = public_uri / image_id config_uri = flat_uri / config_id / config_file_id sidebar_uri = flat_uri / config_id / sidebar_id@@ -35,6 +43,9 @@ comment_uri = flat_uri / comment_id theme_uri = flat_uri / config_id / theme_id +album_uri = image_uri / album_id+topic_uri = flat_uri / topic_id+ data ConfigData = BlogTitle | BlogSubtitle@@ -59,6 +70,8 @@ | SummaryForRoot | SummaryForTag | SummaryForRss+ | PicturePrefix+ | NumberOfLatestPosts deriving (Show) -- unsafe, must restart server after changing config file, sorry about that ...@@ -76,7 +89,7 @@ author_email = for AuthorEmail per_page = for_int' PerPage "7"-navigation = for_list' Navigation "About" .("Home" :)+navigation = for_list' Navigation "About" .(home_nav :) panda_url = "http://www.haskell.org/haskellwiki/Panda" @@ -87,8 +100,8 @@ default_reader = def -load_widget = read_static_widget default_reader >>> purify-sidebar = for_list' Sidebar "" .map (sidebar_uri /).select (file_exist >>> purify )+load_widget = read_static_widget default_reader > purify+sidebar = for_list' Sidebar "" .map (sidebar_uri /).select (file_exist > purify ) .map load_widget footer = for Footer @@ -121,10 +134,13 @@ -- summary cut = for' Cut "✂-----"-parse_boolean = belongs_to ["true", "1", "y", "yes", "yeah"] summary_for_root = for SummaryForRoot .parse_boolean summary_for_tag = for SummaryForTag .parse_boolean summary_for_rss = for SummaryForRss .parse_boolean +-- album+picture_prefix = for' PicturePrefix "\\d+-" +-- latest+number_of_latest_posts = for_int' NumberOfLatestPosts "15"
src/Panda/Controller/Application.hs view
@@ -38,12 +38,12 @@ u = liftIO blog_regex = G.url_date_matcher init_post tags x = x.Tag.fill_tag tags.Comment.fill_comment_size.u-tag_list = Tag.list.u+tag_list = list.u -- main controller -pages = +pages = ( [ ("$" ,index ) , ("(\\?.+)?$" ,index ) , ("rss.xml$" ,index_feed )@@ -53,47 +53,52 @@ , ("tag/." ,tag ) , ("search" ,only_for Search $ search ) , ("comment/create" ,only_for Comment $ comment_create )- ] .map_fst ((G.root /) >>> ("^" ++))+ ] .map_fst ((G.root /) > ("^" ++)) ++ [ ("^" ++ G.root ++ "$" ,index ) , ("^" ++ G.root ++ "?" ,index )- ]- + ] ).map_snd (set_header >>) only_for ext x = if has_extension ext then x else not_found +default_state = do+ latest_posts <- Post.latest G.number_of_latest_posts.u+ def { S.latest_posts = latest_posts } .return+ index = do- posts <- Post.list.u+ posts <- list.u p <- paginate posts tags <- tag_list let nav = if p.Pager.current == 1 then home_nav else no_navigation- let state = def { S.uid = G.post_id, S.pager = p, S.tags = tags, S.nav_location = nav }+ s <- default_state+ let state = s { S.uid = G.post_id, S.tags = tags, S.pager = p, S.nav_location = nav } posts.mapM (init_post tags) ^ PostV.list state >>= output_html index_feed = do- posts <- Post.list.u+ posts <- list.u posts.RSSV.rss "" "" .output blog = do id <- uri ^ Post.uri_to_id- blog <- Post.get id .u- comments <- Comment.list_for id .u+ blog <- get id .u+ comments <- list_for id .u tags <- tag_list test_data <- S.mk_human_test .u- let state = def { S.uid = id, S.tags = tags, S.resource_title = blog.resource_title, S.human_test_data = test_data }+ s <- default_state+ let state = s { S.uid = id, S.tags = tags, S.resource_title = blog.resource_title, S.human_test_data = test_data } blog.(init_post tags) ^ PostV.view state comments >>= output_html static = do id <- uri- static_page <- Static.get id .u+ static_page <- 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 = def { S.uid = id, S.tags = tags, S.nav_location = nav, S.resource_title = static_page.resource_title }+ s <- default_state+ let state = s { 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@@ -103,19 +108,20 @@ case Tag.tag_map' tags .Map.lookup tag_name of Nothing -> not_found Just post_set -> do- posts <- post_set.to_list.rsort.mapM (Post.get) .u >>= mapM (init_post tags)+ posts <- post_set.to_list.rsort.mapM (get) .u >>= mapM (init_post tags) p <- paginate posts- let state = def { S.uid = id, S.pager = p, S.tags = tags, S.resource_title = tag_name.Tag.resource_title_from_name }+ s <- default_state+ let state = s { S.uid = id, S.tags = tags, S.pager = p, S.resource_title = tag_name.Tag.resource_title_from_name } posts.TagV.view state.output_html tag_feed = do- id <- uri ^ (split "/" >>> init >>> join "/")+ id <- uri ^ (split "/" > init > join "/") tags <- tag_list let tag_name = Tag.get_name id case Tag.tag_map' tags .Map.lookup tag_name of Nothing -> not_found Just post_set -> do- posts <- post_set.to_list.rsort.mapM (Post.get) .u ^ map (Tag.fill_tag tags)+ posts <- post_set.to_list.rsort.mapM (get) .u ^ map (Tag.fill_tag tags) posts.RSSV.rss G.tag_id tag_name.output search = do@@ -125,7 +131,8 @@ p <- paginate posts tags <- tag_list query <- uri- let state = def { S.uid = query, S.pager = p, S.tags = tags, S.resource_title = query }+ s' <- default_state+ let state = s' { S.uid = query, S.pager = p, S.tags = tags, S.resource_title = query } posts.mapM (init_post tags) >>= \x -> x.SearchV.view state s.output_html @@ -137,12 +144,12 @@ if [checked, valid_path, exists].and then- inputs >>= (Comment.create >>> u)+ inputs >>= (Comment.create_comment > u) else return () redirect $ (post_id.Post.id_to_uri.u2b).urlEncode -get_input_data = show_data >>> get_input+get_input_data = show_data > get_input check_human = do [l, r, op, h] <- [Comment.LeftNumber, Comment.RightNumber, Comment.Operator, Comment.HumanHack].mapM get_input_data ^ map fromJust@@ -150,9 +157,9 @@ -- create helper, should be refactored to some common aspect check_create =- [ validate Comment.Author ( length >>> (> 0))+ [ validate Comment.Author ( length > (`gt` 0)) , validate Comment.AuthorLink ( const True)- , validate Comment.Body ( length >>> (> 0))+ , validate Comment.Body ( length > (`gt` 0)) , validate Comment.EmptyField ( empty ) , validate Comment.LeftNumber ( belongs_to (S.nums.map show)) , validate Comment.RightNumber ( belongs_to (S.nums.map show))
src/Panda/Helper/Env.hs view
@@ -35,7 +35,7 @@ import Data.Maybe (fromMaybe, fromJust, isJust, isNothing) import System.FilePath hiding ((<.>)) import System.IO.UTF8 (readFile, writeFile)-import Text.XHtml.Strict hiding (select)+import Text.XHtml.Strict hiding (select, sub, meta) import System.Time import System.Directory import Data.Foldable (find)
src/Panda/Helper/Helper.hs view
@@ -10,8 +10,9 @@ import Panda.Helper.PreludeEnv import Network.URI import Network.CGI-import Control.Arrow ((>>>))-import Text.XHtml.Strict hiding (p)++import Text.XHtml.Strict hiding (p, meta)+import qualified Text.XHtml.Strict as Html import Control.Monad hiding (join) import Data.Maybe import qualified Panda.Type.Pager as Pager@@ -21,28 +22,47 @@ import Panda.Type.Reader import System.FilePath.Posix hiding ((<.>)) import System.Time+import qualified Prelude as P+import Data.Default+import Data.Map (Map)+import System.Directory (/) :: FilePath -> FilePath -> FilePath (/) = (</>) infixl 5 / +gt = (P.>)+ -- global parse_config_io s = read_file s ^ (\x -> x.filter_comment.lines.map strip .map (split "\\s*=\\s*") .map fill_snd_blank .map tuple2) where fill_snd_blank [x] = [x,""] fill_snd_blank xs = xs -parse_config = parse_config_io >>> purify+parse_config = parse_config_io > purify write_config_io s xs = xs.map(\(x, y) -> x ++ " = " ++ y) .join "\n" .write_file s -- model-take_extension = takeExtension >>> split "\\." >>> last+take_extension = takeExtension > split "\\." > last+take_known_extension s + | ext.belongs_to exts = ext+ | otherwise = ""+ where + ext = s.take_extension+ exts = readers.only_snd.join'+ drop_known_extension s | s.take_extension.belongs_to exts = dropExtension s | otherwise = s where exts = readers.only_snd.join' +meta = (++ ".meta")++image_extensions = ["jpg", "jpeg", "png", "gif"]+is_image x = image_extensions.any (suffix_of x)+suffix_of = flip isSuffixOf+ -- controller-raw_uri = requestURI ^ (uriPath >>> urlDecode >>> tail >>> remove_trailing_slash )+raw_uri = requestURI ^ (uriPath > urlDecode > tail > remove_trailing_slash ) remove_trailing_slash s = if s.last.is '/' then s.init else s @@ -52,7 +72,7 @@ '?':s -> s.formDecode .map_snd ( unescape_unicode_xml ) .return _ -> return [] -inputs = getInputs ^ map_snd (strip >>> unescape_unicode_xml)+inputs = getInputs ^ map_snd (strip > unescape_unicode_xml > b2u) param_with_default s d = get_param s ^ fromMaybe d input_with_default s d = get_input s ^ fromMaybe d@@ -65,63 +85,103 @@ full_paginate length total = liftM5 ( Pager.Pager length ) current has_next has_previous next previous where current = param_with_default "page" "1" ^ read- has_next = current ^ ( (* length) >>> (< total.from_i) )- has_previous = current ^ (> 1)+ has_next = current ^ ( (* length) > (< total.from_i) )+ has_previous = current ^ (`gt` 1) next = current ^ (+ 1) previous = current ^ (+ (-1)) for_current_page p xs = xs.drop ((p.Pager.current - 1) * p.Pager.length) .take (p.Pager.length) --- id: /type/resoruce-id_to_type x = x.split "/" .first-id_to_resource x = x.split "/" .tail.join "/" no_navigation = "" home_nav = "Home" +default_content_type = "text/html; charset=UTF-8"+set_content_type = setHeader "Content-type"+set_header = set_content_type default_content_type -- view id = identifier css_link l = itag "link" ! [rel "stylesheet", thetype "text/css", href l]+ie_tag x = ("<!--[if IE]>" ++ x.show ++ "<![endif]-->").primHtml+ie6_tag x = ("<!--[if lt IE 7]>" ++ x.show ++ "<![endif]-->").primHtml+ie7_tag x = ("<!--[if IE 7]>" ++ x.show ++ "<![endif]-->").primHtml js_link l = itag "script" ! [thetype "text/javascript", src l] js_src s = tag "script" ! [thetype "text/javascript"] << s rss_link l = itag "link" ! [rel "alternate", thetype "application/rss+xml", href l, title "RSS 2.0"] favicon_link l = itag "link" ! [rel "icon", thetype "image/png", href l]-meta_tag = meta ! [httpequiv "Content-Type", content "text/html; charset=utf-8"]+meta_tag = Html.meta ! [httpequiv "Content-Type", content "text/html; charset=utf-8"] div_id s = thediv ! [id s] div_class s = thediv ! [theclass s] div_class_id x y = thediv ! [theclass x, id y] -output_html = output ... renderHtml+xml_header = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+output_html x = x.renderHtml.output spaced_url = gsub "/" " / " empty_html = toHtml ""+html_if p x = if p then x else empty_html -- config parse_list s = s.split "," .map strip .reject null-not_found = getVar "REQUEST_URI" >>= (fromMaybe "" >>> outputNotFound)+not_found = getVar "REQUEST_URI" >>= (fromMaybe "" > outputNotFound) -- generic-show_data = show >>> snake_case+show_data = show > snake_case+ifM p t f = p >>= (\p' -> if p' then t else f)+parse_boolean = belongs_to ["true", "1", "y", "yes", "yeah"]+mkdir = u2b > createDirectory -- class class DataRenderer a where- render_data :: a -> Html+ render_data :: a -> Html -class Resource a where- resource_title :: a -> String+-- id: /type/resoruce+id_to_type x = x.split "/" .first+id_to_resource x = x.split "/" .tail.join "/"+ +class (Show a) => Resource a where+ resource_title :: a -> String+ resource_type :: a -> String+ resource_type = show_data class Markable a where- markup :: a -> Html+ markup :: a -> Html class Datable a where- date :: a -> CalendarTime+ date :: a -> CalendarTime class Addressable a where- uri :: a -> String+ uri :: a -> String++-- class Pluggable a where+-- render_plugin :: a -> String++class Gettable a where+ get :: String -> IO a++type Assoc = [(String, String)]+ +class Mappable a where+ from_assoc :: Assoc -> IO a++class Creatable a where+ create :: a -> IO ()++class Listable a where+ list :: IO [a]+ list = return []+ + list_for :: String -> IO [a]+ list_for = const list+ cheat_list :: IO [a]+ cheat_list = list++class FlatRead a where+ flat_read :: String -> IO a
src/Panda/Helper/PreludeEnv.hs view
@@ -1,10 +1,18 @@-{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE NoImplicitPrelude, NoMonomorphismRestriction #-} module Panda.Helper.PreludeEnv ( module MPSUTF8 , module Prelude+ , (>)+ , strip ) where -import Prelude hiding ((.), (/), (^), id, span, readFile, writeFile)-import MPSUTF8 hiding (base, col, date, sub)+import Prelude hiding ((.), (/), (^), id, span, readFile, writeFile, div, (>))+import MPSUTF8 hiding (base, col, date, strip)+import qualified MPSUTF8 as MPS+import Control.Arrow ((>>>)) +(>) = (>>>)+infixl 8 >++strip x = MPS.strip (x.u2b) .b2u
src/Panda/Helper/StateHelper.hs view
@@ -19,7 +19,6 @@ uri = raw_uri ^ b2u ^ remove_root -- uri = raw_uri - -- global parse_date format s = case maybe_d of Nothing -> Nothing@@ -36,10 +35,14 @@ only_for ext x = if has_extension ext then x else toHtml "" -- controller-paginate = length >>> full_paginate (G.per_page)+paginate = length > full_paginate (G.per_page) cut = G.cut cut_re = "^\\s*" ++ cut-match_cut = match cut_re-is_cut = match_cut >>> isJust+match_cut = is_cut+is_cut = isInfixOf cut split_cut = split cut_re+++-- model+path id = G.flat_uri / id
+ src/Panda/Helper/ThumbHelper.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE NoImplicitPrelude #-}+module Panda.Helper.ThumbHelper where++-- env+import Panda.Helper.Env hiding (match, body, quote)+import qualified Panda.Config.Global as G+import System.Cmd++convert_if_missing_thumb w x = do+ thumb_exists <- dir_exist (thumb_path)+ if thumb_exists+ then return ()+ else mkdir thumb_path >> convert w x + + where+ thumb_path = x / G.thumb_id++convert w x = do+ images <- ls x ^ filter is_image+ images.map (convert_cmd w x).sequence >>= print+ return ()++quote x = "\"" ++ x ++ "\""++system_utf8 = u2b > system++convert_cmd w p x = do+ system_utf8 $ ["convert -resize", w.show, i, o].join " ".trace'+ where+ i = quote $ p / x + o = quote $ p / G.thumb_id / x
src/Panda/Model/Comment.hs view
@@ -3,7 +3,7 @@ module Panda.Model.Comment where -- env-import Panda.Helper.Env hiding (title, body, size, path, meta)+import Panda.Helper.Env hiding (title, body, size, path) import qualified Panda.Config.Global as G import Panda.Type.Reader import qualified Panda.Model.Post as Post@@ -36,7 +36,7 @@ deriving (Show) instance Resource Comment where- resource_title = uid >>> spaced_url+ resource_title = uid > spaced_url instance Markable Comment where markup x = render_to_html Markdown (x.body)@@ -48,55 +48,64 @@ def = Comment def def def def def -- CRUD-list_for post_id = do- has_comments <- dir_exist d- if has_comments- then ls d ^ reject (isSuffixOf ".meta") ^ rsort ^ map (G.comment_id / r /) >>= mapM get- else return []- where- d = (G.comment_uri / r)- r = post_id.id_to_resource+instance FlatRead Comment where+ flat_read x = do+ t <- x.path.read_file+ def {body = t, uid = x} .return -path id = G.flat_uri / id-meta = (++ ".meta")+instance Gettable Comment where+ get id = do+ x <- flat_read id+ meta <- id.path.meta.parse_config_io+ let at s = meta.lookup (s.show_data) .fromJust+ let author = at Author+ let author_email = at AuthorEmail+ let author_link = at AuthorLink+ x { author = author, author_email = author_email, author_link = author_link } .return -get id = do- meta_exists <- id.path.meta.file_exist- if meta_exists- then get_from_new_format id- else get_from_old_format id+instance Listable Comment where+ list_for resource_id = do+ ifM (dir_exist d) (idsM >>= mapM (get :: String -> IO Comment)) (return [])+ where+ idsM = ls d ^ reject (isSuffixOf ".meta") ^ rsort ^ map (G.comment_id / r /)+ d = (G.comment_uri / r)+ r = resource_id.id_to_resource -get_from_new_format id = do- body <- id.path.read_file- meta <- id.path.meta.parse_config_io- let at s = meta.lookup (s.show_data) .fromJust- let author = at Author- let author_email = at AuthorEmail- let author_link = at AuthorLink- return $ Comment id author body author_email author_link +instance Creatable Comment where+ create = write_to -get_from_old_format id = do- xs <- id.path.read_file ^ lines- let author = xs.first- let author_email = ""- let author_link = xs.drop 2.first- let body = xs.drop 4.unlines- return $ Comment id author body author_email author_link+instance Mappable Comment where+ from_assoc h = do+ let at s = h.lookup (s.show_data) .fromJust+ let body = at Body+ let post_id = at PostId -create h = do- let at s = h.lookup (s.show_data) .fromJust- let body = at Body- let post_id = at PostId+ timestamp <- ( getClockTime >>= toCalendarTime ) ^ format_date G.comment_date_format+ let comment_path = post_id_to_uid post_id + mkdir_p comment_path - timestamp <- ( getClockTime >>= toCalendarTime ) ^ format_date G.comment_date_format- let comment_path = post_id_to_uid post_id - mkdir_p comment_path- - let uid = comment_path / timestamp- write_file uid body- let meta = [Author, AuthorLink, AuthorEmail] .labeling at .map_fst show_data- write_config_io (uid ++ ".meta") meta+ let uid = comment_path / timestamp+ def + { uid = uid+ , body = at Body+ , author = at Author+ , author_email = at AuthorEmail+ , author_link = at AuthorLink+ }+ .return +write_to x = do+ write_file (x.uid) (x.body)+ write_config_io (x.uid.meta) meta_data+ where+ meta_data = + [ (Author, x.author)+ , (AuthorLink, x.author_link)+ , (AuthorEmail, x.author_email)+ ] .map_fst show_data++create_comment x = x.from_assoc >>= (create :: Comment -> IO ())+ -- extra from_post_id x = def { uid = post_id_to_uid x } @@ -104,7 +113,7 @@ uid_to_post_id x = G.post_id / x.split "/" .last fill_comment_size x = do- size <- x.Post.uid.list_for ^ length + size <- x.Post.uid.(list_for :: String -> IO [Comment] ) ^ length return $ x { Post.comment_size = size } gravatar_default_size = size 40
src/Panda/Model/Post.hs view
@@ -13,7 +13,9 @@ import Panda.Type.Reader import Panda.Helper.StateHelper hiding (uri) import Panda.Helper.Helper (date)+import Panda.Model.Helper + data Post = Post { uid :: String -- blog/08-09-04 blog title , title :: String@@ -26,44 +28,65 @@ instance Resource Post where resource_title = title+ resource_type = const G.post_id instance Markable Post where markup x = render_to_html (x.reader) (x.full) instance Datable Post where- date = uid >>> get_date+ date = uid > get_date instance Addressable Post where- uri = uid >>> id_to_uri+ uri = uid > id_to_uri +instance Default Post where+ def = Post def def def def def def++instance FlatRead Post where+ flat_read x = do+ t <- get_body x+ def {body = t, uid = x} .return+ -- CRUD-list = ls G.post_uri ^ rsort ^ map (G.post_id /) >>= mapM get+instance Gettable Post where+ get id = do+ x <- flat_read id+ x {+ title = get_title id+ , reader = get_reader id+ }+ .return -get id = liftM4 (Post id (get_title id) ) (get_body id) (return []) (return 0) (return $ get_reader id)-get_extension = takeExtension+instance Listable Post where+ list = list_ids >>= mapM get+ cheat_list = fast_list++list_ids = ls G.post_uri ^ rsort ^ map (G.post_id /)+fast_list = list_ids ^ map (\x -> def {uid = x, title = x.get_title})++ get_title id = id.words.tail.unwords.drop_known_extension-get_body id = (G.flat_uri / id) .read_file-get_reader id = id.take_extension.guess_reader.fromMaybe G.default_reader 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+match s x = [title, body] .map (send_to x > lower > isInfixOf (s.lower)) .or search "" = return [] search s = list ^ filter (match s) -summary = body >>> split_cut >>> first-full x | x.body.match_cut.isNothing = x.body+summary x = x.body.lines.takeWhile (is_cut > not) .unlines+full x | x.body.match_cut.not = x.body full x = ( xs.takeWhile not_cut ++ xs.dropWhile not_cut .tail ).unlines where- not_cut = is_cut >>> not+ not_cut = is_cut > not xs = x.body.lines -has_continue = body >>> match_cut >>> isJust+has_continue = body > match_cut +latest n = cheat_list ^ take n -- 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+ ext = id.take_known_extension uri_to_id s = G.post_id / (d ++ " " ++ t) where (raw_d, (_, title_with_sep)) = s.MPS.match G.url_date_matcher.fromJust.fst@@ -74,6 +97,8 @@ -- summary markup_summary x = post_summary x +++ next where- post_summary = (reader &&& summary) >>> splash render_to_html- next = if x.has_continue then toHtml $ hotlink (x.uri) << "Read the rest of the post »" else empty_html+ post_summary = (reader &&& summary) > splash render_to_html+ next = if x.has_continue then toHtml $ p << hotlink (x.uri) << "Read the rest of the post »" else empty_html ++
src/Panda/Model/Static.hs view
@@ -5,6 +5,7 @@ import Panda.Helper.Env hiding (match, body) import qualified Panda.Config.Global as G import Panda.Type.Reader+import Panda.Model.Helper data Static = Static { uid :: String@@ -14,15 +15,24 @@ deriving (Show, Eq) instance Resource Static where- resource_title = uid >>> spaced_url+ resource_title x = ("static" / x.uid.get_title) .spaced_url instance Markable Static where markup x = render_to_html (x.reader) (x.body)- ++instance Default Static where+ def = Static def def def+ -- CRUD-get id = liftM2 (Static id) (get_body id) (return $ get_reader id)-get_title = id_to_resource >>> drop_known_extension-get_body id = (G.flat_uri / id) .read_file-get_reader id = id.take_extension.guess_reader.fromMaybe G.default_reader+instance FlatRead Static where+ flat_read x = do+ t <- get_body x+ def {body = t, uid = x} .return -title = uid >>> get_title+instance Gettable Static where+ get id = do+ x <- flat_read id+ x { reader = get_reader id } .return++get_title = id_to_resource > drop_known_extension > split "/" > last+title = uid > get_title
src/Panda/Model/Tag.hs view
@@ -17,23 +17,27 @@ deriving (Show, Eq) instance Resource Tag where- resource_title = uid >>> spaced_url+ resource_title = uid > spaced_url -- CRUD-list = ls G.tag_uri ^ map (G.tag_id /) >>= mapM get+instance Gettable Tag where+ get id = get_resources id ^ Tag id (get_name id) -get id = get_resources id ^ Tag id (get_name id)+instance Listable Tag where+ list = ls G.tag_uri ^ map (G.tag_id /) >>= mapM get++ get_name id = id.split "/" .tail.join' get_resources id = (G.flat_uri / id) .read_file ^ filter_comment ^ lines ^ map (G.post_id / ) ^ to_set -resource_title_from_name = name_to_id >>> spaced_url+resource_title_from_name x = ("tag" / x) .spaced_url tag_map' xs = xs . map (name &&& resources) . to_h tag_map = list ^ tag_map' -for_resource xs x = xs.select (resources >>> has x) .map name+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+sorted xs = xs.sortBy(compare_by (resources > S.size)).reverse name_to_id x = G.tag_id / x
src/Panda/Type/Reader.hs view
@@ -5,9 +5,10 @@ import Panda.Helper.PreludeEnv import qualified Data.Map as Map import Text.Pandoc-import Control.Arrow ((>>>))+ import Text.XHtml.Strict import Data.Default+import Panda.Helper.Html data Reader = Markdown | RST | HTML | Latex deriving (Show, Eq) @@ -27,14 +28,14 @@ guess_reader ext = reader_map.Map.lookup ext -to_html r = r defaultParserState >>> writeHtml defaultWriterOptions+to_html r = r defaultParserState > writeHtml defaultWriterOptions -- this list can go on, as long as there is a library that does -- the convertion. pretty extensible, isn't it. rr :: Reader -> String -> Html rr Markdown = to_html readMarkdown rr RST = to_html readRST-rr HTML = primHtml+rr HTML = escape_html_unicode > primHtml rr Latex = to_html readLaTeX render_to_html = rr
src/Panda/Type/State.hs view
@@ -5,9 +5,10 @@ import Panda.Model.Tag import Random (randomRs, mkStdGen) import System.Time-import Control.Arrow ((>>>))+ import Panda.Helper.PreludeEnv import Data.Default+import Panda.Model.Post data State = State -- model state@@ -19,12 +20,13 @@ , nav_location :: String , resource_title :: String , human_test_data :: HumanTestData+ , latest_posts :: [Post] } deriving (Show) -show_left = human_test_data >>> left >>> show-show_right = human_test_data >>> right >>> show-show_op = human_test_data >>> op >>> display_op+show_left = human_test_data > left > show+show_right = human_test_data > right > show+show_op = human_test_data > op > display_op read_op "+" = Plus read_op "-" = Minus@@ -48,7 +50,7 @@ def = Plus instance Default State where- def = State def def def def def def+ def = State def def def def def def def ops = [Plus, Minus]@@ -65,6 +67,6 @@ where make_sample [a,b,c] = ((get_num a), (get_num b), (get_op c)) make_sample _ = error "human test sample fail"- good_test = splash3 simple_eval >>> belongs_to nums+ good_test = splash3 simple_eval > belongs_to nums get_num n = nums.at (n `mod` (nums.length)) get_op n = ops.at (n `mod` (ops.length))
src/Panda/Type/Theme.hs view
@@ -27,7 +27,7 @@ | Js deriving (Eq, Show, Read) -to_theme xs = Theme +to_theme xs = Theme { name = at Name , container = at Container , header = at Header
src/Panda/View/Atom/Comment.hs view
@@ -7,7 +7,7 @@ import Panda.Type.State hiding (uid) -- model-import Panda.Model.Comment hiding (create)+import Panda.Model.Comment -- render instance DataRenderer Comment where
src/Panda/View/Control/Post.hs view
@@ -21,6 +21,7 @@ import Panda.View.Widget.Template -- entry view+view :: State.State -> [Comment.Comment] -> Post -> Html view state xs x = (x.render_data +++ comment_view ).page state where comment_view = only_for Comment $ CommentV.list xs +++ CommentV.create state (x.uid.Comment.from_post_id)
src/Panda/View/Widget/Template.hs view
@@ -29,6 +29,6 @@ , only_for Analytics $ primHtml (analytics G.analytics_account_id) ] -template t state x = [html_head state, body t state x]+template t state x = [html_head state, body t state x] .toHtml page = template G.theme