By | Chris Done |
At | 2011-07-02 |
Title | Support stepping through expressions with stepeval library. |
Description |
Edit file amelie.cabal 33188 → 33188
41 41 ,feed >= 0.3
42 42 ,download-curl >= 0.1
43 43 ,Diff >= 0.1
44 44 ,css >= 0.1
45 45 ,named-formlet >= 0.1
+ 46 ,stepeval >= 0.2
+ 47 ,haskell-src-exts >= 1.10
… … … … Edit file amelie.conf.sample 33188 → 33188
8 8 [ANNOUNCE]
9 9 user = hpaste
10 10 pass = <YOURPASS>
11 11 host = 127.0.0.1
12 12 port = 6667
+ 13 + 14 [WEB]
+ 15 domain = hpaste:10000
+ 16 + 17 [DEV]
+ 18 commits = https://github.com/chrisdone/amelie/commits/master.atom
+ 19 repo_url = https://github.com/chrisdone/amelie
+ 20 + 21 [STEPEVAL]
+ 22 prelude = wwwroot/hs/stepeval-prelude.hs
… … … … Edit file src/Main.hs 33188 → 33188
14 14 import Amelie.Controller.Home as Home
15 15 import Amelie.Controller.New as New
16 16 import Amelie.Controller.Paste as Paste
17 17 import Amelie.Controller.Raw as Raw
18 18 import Amelie.Controller.Script as Script
+ 19 import Amelie.Controller.Stepeval as Stepeval
+ 20 import Amelie.Controller.Steps as Steps
19 21 import Amelie.Controller.Style as Style
20 22 import Amelie.Model.Announcer (newAnnouncer)
21 23 import Amelie.Types
22 24 import Amelie.Types.Cache
23 25 … … … … 47 49 serve conf p cache ans = route routes where
48 50 routes = [("/css/amelie.css", run Style.handle)
49 51 ,("/js/amelie.js", run Script.handle)
50 52 ,("/css/",serveDirectory "wwwroot/css")
51 53 ,("/js/",serveDirectory "wwwroot/js")
+ 54 ,("/hs/",serveDirectory "wwwroot/hs")
52 55 ,("",run Home.handle)
53 56 ,("/:id",run Paste.handle)
+ 57 ,("/steps/:id",run Steps.handle)
54 58 ,("/raw/:id",run Raw.handle)
55 59 ,("/new",run New.handle)
56 60 ,("/edit/:id",run New.handle)
57 61 ,("/new/:channel",run New.handle)
58 62 ,("/browse/page/:page/offset/:offset",run Browse.handle)
59 63 ,("/browse/page/:page",run Browse.handle)
60 64 ,("/browse",run Browse.handle)
61 65 ,("/activity",run Activity.handle)
62 66 ,("/diff/:this/:that",run Diff.handle)
+ 67 ,("/stepeval/raw",run Stepeval.handleRaw)
+ 68 ,("/stepeval",run Stepeval.handle)
63 69 ]
64 70 run = runHandler conf p cache ans
… … … … Edit file src/Amelie/Config.hs 33188 → 33188
26 26 <- mapM (get c "WEB")
27 27 ["domain"]
28 28 [commits,url]
29 29 <- mapM (get c "DEV")
30 30 ["commits","repo_url"]
+ 31 [prelude]
+ 32 <- mapM (get c "STEPEVAL")
+ 33 ["prelude"]
31 34 32 35 return Config {
33 36 configAnnounce = Announcer user pass host (read port)
34 37 , configPostgres = ConnectInfo pghost (read pgport) pguser pgpass pgdb
35 38 , configDomain = domain
36 39 , configCommits = commits
37 40 , configRepoURL = url
+ 41 , configStepevalPrelude = prelude
38 42 }
39 43 case config of
40 44 Left cperr -> error $ show cperr
41 45 Right config -> return config
… … … … Edit file src/Amelie/Controller.hs 33188 → 33188
8 8 ,output
9 9 ,outputText
10 10 ,goHome
11 11 ,justOrGoHome
12 12 ,getInteger
+ 13 ,getString
13 14 ,getPagination)
14 15 where
15 16 16 17 import Amelie.Types
17 18 import Amelie.Types.Cache
… … … … 62 63 63 64 -- | Get integer parmater.
64 65 getInteger :: ByteString -> Integer -> Controller Integer
65 66 getInteger name def = do
66 67 pid <- (>>= readMay . toString) <$> getParam name
+ 68 maybe (return def) return pid
+ 69 + 70 -- | Get string.
+ 71 getString :: ByteString -> String -> Controller String
+ 72 getString name def = do
+ 73 pid <- (>>= return . toString) <$> getParam name
67 74 maybe (return def) return pid
68 75 69 76 -- | Get pagination data.
70 77 getPagination :: Controller Pagination
71 78 getPagination = do
… … … … Edit file src/Amelie/Types.hs 33188 → 33188
9 9 ,module Amelie.Types.Language
10 10 ,module Amelie.Types.Page
11 11 ,module Amelie.Types.Newtypes
12 12 ,module Amelie.Types.View
13 13 ,module Amelie.Types.Config
- 14 ,module Amelie.Types.Activity)
+ 14 ,module Amelie.Types.Activity
+ 15 ,module Amelie.Types.Stepeval)
15 16 where
16 17 17 18 import Amelie.Types.MVC
18 19 import Amelie.Types.Paste
19 20 import Amelie.Types.Channel
… … … … 21 22 import Amelie.Types.Page
22 23 import Amelie.Types.Newtypes
23 24 import Amelie.Types.View
24 25 import Amelie.Types.Config
25 26 import Amelie.Types.Activity
+ 27 import Amelie.Types.Stepeval
… … … … Edit file src/Amelie/View/Highlight.hs 33188 → 33188
3 3 {-# LANGUAGE RecordWildCards #-}
4 4 5 5 -- | Code highlighting.
6 6 7 7 module Amelie.View.Highlight
- 8 (highlightPaste)
+ 8 (highlightPaste
+ 9 ,highlightHaskell)
9 10 where
10 11 11 12 import Amelie.Types
12 13 import Amelie.View.Html
13 14 14 15 import Data.List (find)
- 15 import Data.Text (unpack)
+ 16 import Data.Text (Text,unpack)
16 17 import Language.Haskell.HsColour.CSS (hscolour)
17 18 import Prelude hiding ((++))
18 19 import Text.Blaze.Html5 as H hiding (map)
19 20 20 21 -- | Syntax highlight the paste.
… … … … 23 24 H.table ! aClass "code" $
24 25 td $
25 26 case lang of
26 27 Just (Language{languageName="haskell"}) ->
27 28 preEscapedString $ hscolour False (unpack pastePaste)
- 28 _ -> pre $ code $ toHtml pastePaste
+ 29 _ -> pre $ toHtml pastePaste
29 30 30 31 where lang = find ((==pasteLanguage) . Just . languageId) langs
+ 32 + 33 highlightHaskell :: Text -> Html
+ 34 highlightHaskell paste =
+ 35 H.table ! aClass "code" $
+ 36 td $ preEscapedString $ hscolour False (unpack paste)
… … … … Edit file src/Amelie/View/Hlint.hs 33188 → 33188
3 3 {-# LANGUAGE RecordWildCards #-}
4 4 5 5 -- | Show hlint suggestions.
6 6 7 7 module Amelie.View.Hlint
- 8 (viewHints)
+ 8 (viewHints
+ 9 ,viewSuggestions)
9 10 where
10 11 11 12 import Amelie.Types
12 13 import Amelie.View.Html
13 14 … … … … 26 27 Ignore -> \_ -> return ()
27 28 Warning -> warnNoTitleSection
28 29 Error -> errorNoTitleSection
29 30 lns = lines $ clean $ hintContent hint
30 31 clean = dropWhile (==':') . dropWhile (/=':')
+ 32 + 33 viewSuggestions :: [Suggestion] -> Html
+ 34 viewSuggestions = viewHints . map toHint where
+ 35 toHint s = Hint (suggestionSeverity s)
+ 36 (show s)
… … … … Edit file src/Amelie/View/Paste.hs 33188 → 33188
125 125 126 126 -- | List the details of the page in a dark section.
127 127 pasteDetails :: [Paste] -> [Channel] -> [Language] -> Paste -> Html
128 128 pasteDetails pastes chans langs paste@Paste{..} =
129 129 darkNoTitleSection $ do
- 130 pasteNav pastes paste
+ 130 pasteNav langs pastes paste
131 131 h2 $ toHtml $ fromStrict pasteTitle
132 132 ul ! aClass "paste-specs" $ do
133 133 detail "Paste" $ pasteLink paste $ "#" ++ show pasteId
134 134 detail "Author" $ pasteAuthor
135 135 detail "Language" $ showLanguage langs pasteLanguage
… … … … 140 140 141 141 where detail title content = do
142 142 li $ do strong (title ++ ":"); toHtml content
143 143 144 144 -- | Individual paste navigation.
- 145 pasteNav :: [Paste] -> Paste -> Html
- 146 pasteNav pastes paste =
+ 145 pasteNav :: [Language] -> [Paste] -> Paste -> Html
+ 146 pasteNav langs pastes paste =
147 147 H.div ! aClass "paste-nav" $ do
148 148 diffLink
+ 149 stepsLink
149 150 href ("/edit/" ++ pack (show pid) ++ "") ("Annotate" :: Text)
150 151 151 152 where pid = pasteId paste
152 153 pairs = zip (drop 1 pastes) pastes
153 154 parent = fmap snd $ find ((==pid).pasteId.fst) $ pairs
… … … … 156 157 Nothing -> return ()
157 158 Just Paste{pasteId=prevId} -> do
158 159 href ("/diff/" ++ show prevId ++ "/" ++ show pid)
159 160 ("Diff" :: Text)
160 161 " - "
+ 162 stepsLink
+ 163 | lang == Just "haskell" = do href ("/steps/" ++ show pid)
+ 164 ("Steps" :: Text)
+ 165 " - "
+ 166 | otherwise = return ()
+ 167 lang = pasteLanguage paste >>= (`lookup` ls)
+ 168 ls = map (languageId &&& languageName) langs
161 169 162 170 -- | Show the paste content with highlighting.
163 171 pasteContent :: [Language] -> Paste -> Html
164 172 pasteContent langs paste =
165 173 lightNoTitleSection $ highlightPaste langs paste
… … … … Edit file src/Amelie/View/Style.hs 33188 → 33188
173 173 174 174 -- | Styles for the highlighter.
175 175 highlighter :: CSS Rule
176 176 highlighter = do
177 177 diff
+ 178 classRule "steps" $ do
+ 179 marginTop "1em"
+ 180 classRule "steps-expr" $ do
+ 181 rule ".text" $ do
+ 182 width "300px"
178 183 classRule "code" $ do
179 184 tokens
180 185 lineNumbers
181 186 182 187 rule "pre" $ do
… … … … 205 210 tokenColor "keyword" "#397460"
206 211 tokenColor "str" "#366354"
207 212 tokenColor "conid" "#4F4371"
208 213 tokenColor "varop" "#333"
209 214 tokenColor "varid" "#333"
+ 215 tokenColor "num" "#4F4371"
210 216 rule "pre" $ do
211 217 rule ".diff" $ do
212 218 color "#555"
213 219 rule "code" $ do
214 220 jcolor "title" "#333"
… … … … Edit file src/Amelie/Types/Cache.hs 33188 → 33188
13 13 14 14 data Key =
15 15 Home
16 16 | Paste Integer
17 17 | Activity
+ 18 | Steps Integer String -- Expr
18 19 deriving (Eq,Ord)
19 20 20 21 data Cache =
21 22 Cache {
22 23 cacheMap :: MVar (Map Key Text)
… … … … Edit file src/Amelie/Types/Config.hs 33188 → 33188
7 7 8 8 import Database.PostgreSQL.Simple (ConnectInfo)
9 9 10 10 -- | Site-wide configuration.
11 11 data Config = Config {
- 12 configAnnounce :: Announcer
- 13 , configPostgres :: ConnectInfo
- 14 , configDomain :: String
- 15 , configCommits :: String
- 16 , configRepoURL :: String
+ 12 configAnnounce :: Announcer
+ 13 , configPostgres :: ConnectInfo
+ 14 , configDomain :: String
+ 15 , configCommits :: String
+ 16 , configRepoURL :: String
+ 17 , configStepevalPrelude :: FilePath
17 18 } deriving (Show)
18 19 19 20 -- | Announcer configuration.
20 21 data Announcer = Announcer {
21 22 announceUser :: String
… … … … Edit file src/Amelie/Types/Paste.hs 33188 → 33188
7 7 8 8 module Amelie.Types.Paste
9 9 (Paste(..)
10 10 ,PasteSubmit(..)
11 11 ,PasteFormlet(..)
+ 12 ,ExprFormlet(..)
12 13 ,PastePage(..)
+ 14 ,StepsPage(..)
13 15 ,Hint(..))
14 16 where
15 17 16 18 import Amelie.Types.Newtypes
17 19 import Amelie.Types.Language
… … … … 25 27 import Database.PostgreSQL.Simple.QueryResults (QueryResults(..))
26 28 import Database.PostgreSQL.Simple.Result (Result(..))
27 29 import Language.Haskell.HLint (Severity)
28 30 import Snap.Types (Params)
29 31 import Text.Blaze (ToHtml(..),toHtml)
+ 32 import Text.Blaze.Html5 (Html)
30 33 31 34 -- | A paste.
32 35 data Paste = Paste {
33 36 pasteId :: PasteId
34 37 ,pasteTitle :: Text
… … … … 78 81 , pfChannels :: [Channel]
79 82 , pfDefChan :: Maybe Text
80 83 , pfEditPaste :: Maybe Paste
81 84 }
82 85 + 86 data ExprFormlet = ExprFormlet {
+ 87 efSubmitted :: Bool
+ 88 , efParams :: Params
+ 89 }
+ 90 83 91 data PastePage = PastePage {
84 92 ppPaste :: Paste
85 93 , ppChans :: [Channel]
86 94 , ppLangs :: [Language]
87 95 , ppHints :: [Hint]
88 96 , ppAnnotations :: [Paste]
89 97 , ppAnnotationHints :: [[Hint]]
+ 98 }
+ 99 + 100 data StepsPage = StepsPage {
+ 101 spPaste :: Paste
+ 102 , spChans :: [Channel]
+ 103 , spLangs :: [Language]
+ 104 , spHints :: [Hint]
+ 105 , spSteps :: [Text]
+ 106 , spAnnotations :: [Paste]
+ 107 , spAnnotationHints :: [[Hint]]
+ 108 , spForm :: Html
90 109 }
91 110 92 111 instance Param Severity where
93 112 render = Escape . toByteString . Utf8.fromString . show
94 113 {-# INLINE render #-}
… … … … Edit file src/Amelie/Model/Paste.hs 33188 → 33188
- 1 {-# OPTIONS -Wall #-}
+ 1 {-# OPTIONS -Wall -fno-warn-name-shadowing #-}
2 2 {-# LANGUAGE OverloadedStrings #-}
3 3 {-# LANGUAGE RecordWildCards #-}
4 4 {-# LANGUAGE ScopedTypeVariables #-}
5 5 {-# LANGUAGE ViewPatterns #-}
6 6 … … … … 12 12 ,createOrEdit
13 13 ,createPaste
14 14 ,getAnnotations
15 15 ,getSomePastes
16 16 ,countPublicPastes
+ 17 ,generateHints
+ 18 ,generateSteps
17 19 ,getHints)
18 20 where
19 21 20 22 import Amelie.Types
21 23 import Amelie.Model
22 24 import Amelie.Model.Announcer
23 25 - 24 import Control.Applicative ((<$>),(<|>))
+ 26 import Control.Applicative ((<$>),(<|>))
+ 27 import Control.Exception (handle,SomeException)
25 28 import Control.Monad
26 29 import Control.Monad.Env
27 30 import Control.Monad.IO
28 31 import Data.Char
- 29 import Data.List (find,intercalate)
- 30 import Data.Maybe (fromMaybe,listToMaybe)
- 31 import Data.Monoid.Operator ((++))
- 32 import Data.Text (Text,unpack,pack)
- 33 import Data.Text.IO as T (writeFile)
- 34 import Data.Text.Lazy (fromStrict)
+ 32 import Data.List (find,intercalate)
+ 33 import Data.Maybe (fromMaybe,listToMaybe)
+ 34 import Data.Monoid.Operator ((++))
+ 35 import Data.Text (Text,unpack,pack)
+ 36 import Data.Text.IO as T (writeFile)
+ 37 import Data.Text.Lazy (fromStrict)
+ 38 import Language.Haskell.Exts
35 39 import Language.Haskell.HLint
- 36 import Prelude hiding ((++))
+ 40 import Language.Haskell.Stepeval
+ 41 import Prelude hiding ((++))
37 42 import System.Directory
38 43 import System.FilePath
39 44 40 45 -- | Count public pastes.
41 46 countPublicPastes :: Model Integer
… … … … 109 114 just j m = maybe (return ()) m j
110 115 111 116 -- | Create the hints for a paste.
112 117 createHints :: PasteSubmit -> PasteId -> Model ()
113 118 createHints ps pid = do
- 114 hints <- generateHints ps pid
+ 119 hints <- generateHintsForPaste ps pid
115 120 forM_ hints $ \hint ->
116 121 exec ["INSERT INTO hint"
117 122 ,"(paste,type,content)"
118 123 ,"VALUES"
119 124 ,"(?,?,?)"]
… … … … 142 147 validNick :: String -> Bool
143 148 validNick s = first && all ok s && length s > 0 where
144 149 ok c = isDigit c || isLetter c || elem c "-_/\\;()[]{}?`'"
145 150 first = all (\c -> isDigit c || isLetter c) $ take 1 s
146 151 - 147 -- | Get hints for a Haskell paste from hlint.
- 148 generateHints :: PasteSubmit -> PasteId -> Model [Suggestion]
- 149 generateHints PasteSubmit{..} (fromIntegral -> pid :: Integer) = io $ do
+ 152 -- | Get the steps.
+ 153 generateSteps :: Text -> String -> Model [Text]
+ 154 generateSteps mod expr = do
+ 155 prelude <- getPrelude (unpack mod)
+ 156 case parseExp expr of
+ 157 ParseOk e -> io $ handle (\(x :: SomeException) -> return (err x)) $ do
+ 158 let steps = map format $ take 50 $ itereval prelude e
+ 159 !_ = length $ show $ concat steps
+ 160 return $ map pack steps
+ 161 ParseFailed _ g -> return ["Error: " ++ pack g]
+ 162 + 163 where format = prettyPrint
+ 164 err e = ["Error: " ++ pack (show e)]
+ 165 + 166 -- | Get the stepeval prelude.
+ 167 getPrelude :: String -> Model [Decl]
+ 168 getPrelude extra = do
+ 169 c <- env modelStateConfig
+ 170 io $ handle (\e -> return [] `const` (e :: SomeException)) $ do
+ 171 result <- readFile (configStepevalPrelude c)
+ 172 case parseModule (result ++ "\n\n" ++ extra) of
+ 173 ParseOk (Module _ _ _ _ _ _ ds) -> return ds
+ 174 _ -> return []
+ 175 + 176 -- | Get hints for a Haskell paste from hlint.
+ 177 generateHintsForPaste :: PasteSubmit -> PasteId -> Model [Suggestion]
+ 178 generateHintsForPaste PasteSubmit{..} (fromIntegral -> pid :: Integer) =
+ 179 generateHints (show pid) pasteSubmitPaste
+ 180 + 181 -- | Get hints for a Haskell paste from hlint.
+ 182 generateHints :: FilePath -> Text -> Model [Suggestion]
+ 183 generateHints pid contents = io $ do
150 184 tmpdir <- getTemporaryDirectory
- 151 let tmp = tmpdir </> show pid ++ ".hs"
+ 185 let tmp = tmpdir </> pid ++ ".hs"
152 186 exists <- doesFileExist tmp
- 153 unless exists $ T.writeFile tmp $ pasteSubmitPaste
+ 187 unless exists $ T.writeFile tmp $ contents
154 188 hints <- hlint [tmp,"--quiet","--ignore=Parse error"]
155 189 return hints
156 190 157 191 getHints :: PasteId -> Model [Hint]
158 192 getHints pid =
… … … … Add file wwwroot/hs/stepeval-prelude.hs 33188
+ 1 module Prelude where + 2 + 3 -- Arithmetic operations and some other primitives are builtin + 4 -- Type signatures are entirely useless here. + 5 -- Guards on the rhs of function equations are not supported, but in case + 6 -- expressions they are. + 7 + 8 -- combinators + 9 id x = x + 10 + 11 const x _ = x + 12 + 13 f $ x = f x + 14 -- infixr 0 $ + 15 + 16 flip f x y = f y x + 17 + 18 (f . g) x = f (g x) + 19 -- infixr 9 . + 20 + 21 fix f = let x = f x in x + 22 + 23 -- booleans + 24 not True = False + 25 not False = True + 26 + 27 True || _ = True + 28 False || b = b + 29 -- infixr 2 || + 30 + 31 False && _ = False + 32 True && b = b + 33 -- infixr 3 && + 34 + 35 -- tuples + 36 fst (x, _) = x + 37 snd (_, x) = x + 38 + 39 curry f x y = f (x, y) + 40 uncurry f (x, y) = f x y + 41 + 42 -- lists + 43 foldr _ z [] = z + 44 foldr f z (x:xs) = x `f` foldr f z xs + 45 + 46 foldl _ acc [] = acc + 47 foldl f acc (x:xs) = foldl f (f acc x) xs + 48 -- foldl f z xs = foldr (\x r z -> r (f z x)) id xs z + 49 + 50 null [] = True + 51 null _ = False + 52 + 53 map f [] = [] + 54 map f (x:xs) = f x : map f xs + 55 + 56 head (x:_) = x + 57 tail (_:xs) = xs + 58 + 59 [] ++ ys = ys + 60 (x:xs) ++ ys = x : (xs ++ ys) + 61 -- infixr 5 ++ + 62 + 63 take n xs = if n <= 0 then [] + 64 else case xs of + 65 [] -> [] + 66 y:ys -> y : take (n - 1) ys + 67 + 68 repeat x = let xs = x : xs in xs + 69 + 70 drop n xs = if n <= 0 || null xs + 71 then xs + 72 else drop (n - 1) (tail xs) + 73 + 74 length [] = 0 + 75 length (x:xs) = 1 + length xs + 76 + 77 scanl f z [] = [z] + 78 scanl f z (x:xs) = z : scanl f (f z x) xs + 79 + 80 reverse = foldl (flip (:)) [] + 81 + 82 zipWith _ [] _ = [] + 83 zipWith _ _ [] = [] + 84 zipWith (*) (x:xs) (y:ys) = x * y : zipWith (*) xs ys + 85 + 86 iterate f x = x : iterate f (f x) Add file src/Amelie/View/Stepeval.hs 33188
+ 1 {-# OPTIONS -Wall -fno-warn-name-shadowing -fno-warn-unused-do-bind #-} + 2 {-# LANGUAGE OverloadedStrings #-} + 3 {-# LANGUAGE RecordWildCards #-} + 4 + 5 -- | Stepeval explanation view. + 6 + 7 module Amelie.View.Stepeval + 8 (page) + 9 where + 10 + 11 import Amelie.Types + 12 import Amelie.View.Highlight + 13 import Amelie.View.Hlint + 14 import Amelie.View.Html + 15 import Amelie.View.Layout + 16 + 17 import Data.Monoid.Operator ((++)) + 18 import Data.Text (Text) + 19 import Language.Haskell.HLint + 20 import Prelude hiding ((++)) + 21 import Text.Blaze.Html5 as H hiding (map) + 22 + 23 -- | Render the page page. + 24 page :: StepevalPage -> Html + 25 page StepevalPage{..} = + 26 layoutPage $ Page { + 27 pageTitle = "Stepeval support" + 28 , pageBody = do explanation + 29 viewPaste sePaste seHints + 30 , pageName = "paste" + 31 } + 32 + 33 explanation :: Html + 34 explanation = do + 35 lightSection "Stepeval" $ do + 36 p $ do "A program/library for evaluating " + 37 "a Haskell expression step-by-step. This web site uses it " + 38 "for stepping through provided expressions." + 39 p $ href ("https://github.com/benmachine/stepeval" :: Text) + 40 ("Repository for Stepeval" :: Text) + 41 p $ do "Stepeval comes with a simple Prelude of pure functions " + 42 "(see below) that can be used when stepping through " + 43 "expressions. This may be expanded upon in the future." + 44 p $ do "This web site will automatically include declarations " + 45 "from the paste as the expression to be evaluted." + 46 + 47 -- | View a paste's details and content. + 48 viewPaste :: Text -> [Suggestion] -> Html + 49 viewPaste paste hints = do + 50 pasteDetails "Stepeval Prelude" + 51 pasteContent paste + 52 viewSuggestions hints + 53 + 54 -- | List the details of the page in a dark section. + 55 pasteDetails :: Text -> Html + 56 pasteDetails title = + 57 darkNoTitleSection $ do + 58 pasteNav + 59 h2 $ toHtml title + 60 ul ! aClass "paste-specs" $ do + 61 detail "Language" $ "Haskell" + 62 detail "Raw" $ href ("/stepeval/raw" :: Text) + 63 ("View raw link" :: Text) + 64 clear + 65 + 66 where detail title content = do + 67 li $ do strong (title ++ ":"); content + 68 + 69 -- | Individual paste navigation. + 70 pasteNav :: Html + 71 pasteNav = + 72 H.div ! aClass "paste-nav" $ do + 73 href ("https://github.com/benmachine/stepeval" :: Text) + 74 ("Go to stepeval project" :: Text) + 75 + 76 -- | Show the paste content with highlighting. + 77 pasteContent :: Text -> Html + 78 pasteContent paste = + 79 lightNoTitleSection $ + 80 highlightHaskell paste Add file src/Amelie/View/Steps.hs 33188
+ 1 {-# OPTIONS -Wall -fno-warn-name-shadowing #-} + 2 {-# LANGUAGE OverloadedStrings #-} + 3 {-# LANGUAGE RecordWildCards #-} + 4 + 5 -- | Paste steps view. + 6 + 7 module Amelie.View.Steps + 8 (page + 9 ,exprFormlet) + 10 where + 11 + 12 import Amelie.Types + 13 import Amelie.View.Highlight + 14 import Amelie.View.Hlint (viewHints) + 15 import Amelie.View.Html + 16 import Amelie.View.Layout + 17 import Amelie.View.Paste (pasteLink) + 18 + 19 import Control.Monad + 20 import Data.Monoid.Operator ((++)) + 21 import Data.Text (Text) + 22 import qualified Data.Text as T + 23 import Data.Text.Lazy (fromStrict) + 24 import Prelude hiding ((++),div) + 25 import Text.Blaze.Html5 as H hiding (map) + 26 import qualified Text.Blaze.Html5.Attributes as A + 27 import Text.Formlet + 28 + 29 -- | Render the steps page. + 30 page :: StepsPage -> Html + 31 page StepsPage{spPaste=p@Paste{..},..} = + 32 layoutPage $ Page { + 33 pageTitle = pasteTitle + 34 , pageBody = viewPaste spForm p spHints spSteps + 35 , pageName = "steps" + 36 } + 37 + 38 -- | View a paste's details and content. + 39 viewPaste :: Html -> Paste -> [Hint] -> [Text] -> Html + 40 viewPaste form paste@Paste{..} hints steps = do + 41 case pasteParent of + 42 Nothing -> return () + 43 Just{} -> let an = "a" ++ show (fromIntegral pasteId :: Integer) + 44 in a ! A.name (toValue an) $ return () + 45 pasteDetails paste + 46 pasteContent paste + 47 stepsForm form + 48 viewSteps steps + 49 viewHints hints + 50 + 51 stepsForm :: Html -> Html + 52 stepsForm form = + 53 lightNoTitleSection $ + 54 div ! aClass "steps-expr" $ + 55 form + 56 + 57 -- | A formlet for expr submission / editing. + 58 exprFormlet :: ExprFormlet -> (Formlet Text,Html) + 59 exprFormlet ExprFormlet{..} = + 60 let frm = form $ do + 61 formletHtml exprSubmit efParams + 62 submitInput "submit" "Submit" + 63 in (exprSubmit,frm) + 64 + 65 exprSubmit :: Formlet Text + 66 exprSubmit = req (textInput "expr" "Expression" Nothing) + 67 + 68 viewSteps :: [Text] -> Html + 69 viewSteps steps = + 70 lightSection "Steps (displaying 50 max.)" $ + 71 div ! aClass "steps" $ do + 72 highlightHaskell $ T.intercalate "\n\n" steps + 73 + 74 -- | List the details of the page in a dark section. + 75 pasteDetails :: Paste -> Html + 76 pasteDetails paste@Paste{..} = + 77 darkNoTitleSection $ do + 78 pasteNav + 79 h2 $ toHtml $ fromStrict pasteTitle + 80 ul ! aClass "paste-specs" $ do + 81 detail "Paste" $ pasteLink paste $ "#" ++ show pasteId + 82 detail "Author" $ pasteAuthor + 83 clear + 84 + 85 where detail title content = do + 86 li $ do strong (title ++ ":"); toHtml content + 87 + 88 -- | Individual paste navigation. + 89 pasteNav :: Html + 90 pasteNav = + 91 H.div ! aClass "paste-nav" $ do + 92 href ("/stepeval" :: Text) + 93 ("About evaluation step support" :: Text) + 94 + 95 -- | Show the paste content with highlighting. + 96 pasteContent :: Paste -> Html + 97 pasteContent paste = + 98 lightNoTitleSection $ highlightHaskell (pastePaste paste) Add file src/Amelie/Types/Stepeval.hs 33188
+ 1 {-# OPTIONS -Wall -fno-warn-orphans #-} + 2 {-# LANGUAGE RecordWildCards #-} + 3 {-# LANGUAGE GeneralizedNewtypeDeriving #-} + 4 {-# LANGUAGE OverloadedStrings #-} + 5 + 6 -- | The stepeval types. + 7 + 8 module Amelie.Types.Stepeval + 9 (StepevalPage(..)) + 10 where + 11 + 12 import Data.Text (Text) + 13 import Language.Haskell.HLint + 14 + 15 data StepevalPage = StepevalPage { + 16 sePaste :: Text + 17 , seHints :: [Suggestion] + 18 } Add file src/Amelie/Controller/Stepeval.hs 33188
+ 1 {-# OPTIONS -Wall -fno-warn-name-shadowing #-} + 2 {-# LANGUAGE OverloadedStrings #-} + 3 {-# LANGUAGE ScopedTypeVariables #-} + 4 + 5 -- | Stepeval explanation controller. + 6 + 7 module Amelie.Controller.Stepeval + 8 (handle + 9 ,handleRaw) + 10 where + 11 + 12 import Amelie.Types + 13 + 14 import Amelie.Controller + 15 import Amelie.Model + 16 import Amelie.Model.Paste + 17 import Amelie.View.Stepeval (page) + 18 + 19 import Control.Monad.Env + 20 import Control.Monad.IO + 21 import qualified Data.Text.IO as T + 22 import Data.Text.Lazy (fromStrict) + 23 import Prelude hiding ((++)) + 24 import Snap.Types + 25 + 26 -- | Handle the stepeval explanation page. + 27 handle :: Controller () + 28 handle = do + 29 conf <- env controllerStateConfig + 30 contents <- io $ T.readFile $ configStepevalPrelude conf + 31 hints <- model $ generateHints "stepeval" contents + 32 output $ page StepevalPage { + 33 seHints = hints + 34 , sePaste = contents + 35 } + 36 + 37 -- | Handle the raw stepeval Prelude view. + 38 handleRaw :: Controller () + 39 handleRaw = do + 40 modifyResponse $ setContentType "text/plain; charset=UTF-8" + 41 conf <- env controllerStateConfig + 42 contents <- io $ T.readFile $ configStepevalPrelude conf + 43 outputText . fromStrict $ contents Add file src/Amelie/Controller/Steps.hs 33188
+ 1 {-# OPTIONS -Wall -fno-warn-name-shadowing #-} + 2 {-# LANGUAGE OverloadedStrings #-} + 3 {-# LANGUAGE ScopedTypeVariables #-} + 4 + 5 -- | Paste controller. + 6 + 7 module Amelie.Controller.Steps + 8 (handle) + 9 where + 10 + 11 import Amelie.Types + 12 + 13 import Amelie.Controller + 14 import Amelie.Controller.Paste (getPasteId) + 15 import Amelie.Controller.Cache (cache) + 16 import Amelie.Model + 17 import Amelie.Model.Channel (getChannels) + 18 import Amelie.Model.Language (getLanguages) + 19 import Amelie.Model.Paste + 20 import Amelie.Types.Cache as Key + 21 import Amelie.View.Steps (page,exprFormlet) + 22 + 23 import Data.Maybe + 24 import Prelude hiding ((++)) + 25 import Snap.Types + 26 import Text.Formlet + 27 import Control.Applicative + 28 import Data.Text (unpack) + 29 import Text.Blaze.Html5 as H hiding (output) + 30 + 31 -- | Handle the paste page. + 32 handle :: Controller () + 33 handle = do + 34 pid <- getPasteId + 35 justOrGoHome pid $ \(pid :: Integer) -> do + 36 (form,expr) <- exprForm + 37 html <- cache (Key.Steps pid expr) $ do + 38 paste <- model $ getPasteById (fromIntegral pid) + 39 case paste of + 40 Nothing -> return Nothing + 41 Just paste -> do + 42 hints <- model $ getHints (pasteId paste) + 43 steps <- model $ generateSteps (pastePaste paste) expr + 44 pastes <- model $ getAnnotations (fromIntegral pid) + 45 ahints <- model $ mapM (getHints.pasteId) pastes + 46 chans <- model $ getChannels + 47 langs <- model $ getLanguages + 48 return $ Just $ page StepsPage { + 49 spChans = chans + 50 , spLangs = langs + 51 , spAnnotations = pastes + 52 , spHints = hints + 53 , spSteps = steps + 54 , spPaste = paste + 55 , spAnnotationHints = ahints + 56 , spForm = form + 57 } + 58 justOrGoHome html outputText + 59 + 60 -- | Control paste editing / submission. + 61 exprForm :: Controller (Html,String) + 62 exprForm = do + 63 params <- getParams + 64 submitted <- isJust <$> getParam "submit" + 65 let formlet = ExprFormlet { + 66 efSubmitted = submitted + 67 , efParams = params + 68 } + 69 (getValue,_) = exprFormlet formlet + 70 value = formletValue getValue params + 71 (_,html) = exprFormlet formlet + 72 val = either (const Nothing) Just $ value + 73 return (html,maybe "" unpack val) Add file lib/stepeval 40960
+ 1 /home/chris/Projects/me/stepeval