├── static ├── favicon.ico ├── js │ ├── lang-go.js │ ├── lang-lua.js │ ├── lang-ml.js │ ├── lang-sql.js │ ├── lang-tex.js │ ├── lang-vb.js │ ├── lang-vhdl.js │ ├── lang-wiki.js │ ├── lang-apollo.js │ ├── lang-scala.js │ ├── lang-proto.js │ ├── lang-yaml.js │ ├── application.js │ ├── lang-hs.js │ ├── lang-lisp.js │ ├── lang-css.js │ ├── lang-n.js │ ├── flash.js │ ├── lang-clj.js │ ├── jquery.cookie.js │ ├── prettify.js │ ├── bootstrap.min.js │ ├── lang-xq.js │ └── bootstrap.js ├── img │ ├── glyphicons-halflings.png │ └── glyphicons-halflings-white.png └── css │ ├── prettify.css │ └── application.css ├── Procfile ├── README ├── .gitignore ├── database.conf ├── mime.types ├── .env ├── Controllers.hs ├── Controllers ├── Welcome.hs └── Repo.hs ├── Models.hs ├── LICENSE ├── Views ├── Welcome.hs └── Repo.hs ├── Viewer.hs ├── gitstar-viewer.cabal ├── Layouts.hs ├── Utils.hs └── Data └── UniDiff.hs /static/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: hails 2 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ## Gitstar Code Viewer App 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.hi 3 | *.swp 4 | Main 5 | *.swp 6 | -------------------------------------------------------------------------------- /database.conf: -------------------------------------------------------------------------------- 1 | gitstar-policy-0.1.2:Gitstar.Policy.GitstarPolicy,_gitstar,gitstar_db 2 | -------------------------------------------------------------------------------- /mime.types: -------------------------------------------------------------------------------- 1 | text/x-markdown md markdown 2 | text/x-javascript js 3 | text/x-cabal cabal 4 | -------------------------------------------------------------------------------- /static/js/lang-go.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scslab/gitstar-viewer/master/static/js/lang-go.js -------------------------------------------------------------------------------- /static/js/lang-lua.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scslab/gitstar-viewer/master/static/js/lang-lua.js -------------------------------------------------------------------------------- /static/js/lang-ml.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scslab/gitstar-viewer/master/static/js/lang-ml.js -------------------------------------------------------------------------------- /static/js/lang-sql.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scslab/gitstar-viewer/master/static/js/lang-sql.js -------------------------------------------------------------------------------- /static/js/lang-tex.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scslab/gitstar-viewer/master/static/js/lang-tex.js -------------------------------------------------------------------------------- /static/js/lang-vb.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scslab/gitstar-viewer/master/static/js/lang-vb.js -------------------------------------------------------------------------------- /static/js/lang-vhdl.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scslab/gitstar-viewer/master/static/js/lang-vhdl.js -------------------------------------------------------------------------------- /static/js/lang-wiki.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scslab/gitstar-viewer/master/static/js/lang-wiki.js -------------------------------------------------------------------------------- /static/js/lang-apollo.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scslab/gitstar-viewer/master/static/js/lang-apollo.js -------------------------------------------------------------------------------- /static/js/lang-scala.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scslab/gitstar-viewer/master/static/js/lang-scala.js -------------------------------------------------------------------------------- /static/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scslab/gitstar-viewer/master/static/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /static/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scslab/gitstar-viewer/master/static/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | APP_NAME=Viewer 2 | PORT=8081 3 | DATABASE_CONFIG_FILE=database.conf 4 | HMAC_KEY=w00t 5 | AUTH_URL=http://auth.gitstar.com 6 | HAILS_CJAIL_DIR=/opt/cjail/gitstar-viewer 7 | HAILS_CJAIL_TIMEOUT=2 8 | -------------------------------------------------------------------------------- /Controllers.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | #if PRODUCTION 3 | {-# LANGUAGE Safe #-} 4 | #endif 5 | 6 | module Controllers ( module Controllers.Welcome 7 | , module Controllers.Repo 8 | ) where 9 | 10 | import Controllers.Welcome 11 | import Controllers.Repo 12 | -------------------------------------------------------------------------------- /static/js/lang-proto.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.sourceDecorator({keywords:"bytes,default,double,enum,extend,extensions,false,group,import,max,message,option,optional,package,repeated,required,returns,rpc,service,syntax,to,true",types:/^(bool|(double|s?fixed|[su]?int)(32|64)|float|string)\b/,cStyleComments:!0}),["proto"]); 2 | -------------------------------------------------------------------------------- /static/js/lang-yaml.js: -------------------------------------------------------------------------------- 1 | var a=null; 2 | PR.registerLangHandler(PR.createSimpleLexer([["pun",/^[:>?|]+/,a,":|>?"],["dec",/^%(?:YAML|TAG)[^\n\r#]+/,a,"%"],["typ",/^&\S+/,a,"&"],["typ",/^!\S*/,a,"!"],["str",/^"(?:[^"\\]|\\.)*(?:"|$)/,a,'"'],["str",/^'(?:[^']|'')*(?:'|$)/,a,"'"],["com",/^#[^\n\r]*/,a,"#"],["pln",/^\s+/,a," \t\r\n"]],[["dec",/^(?:---|\.\.\.)(?:[\n\r]|$)/],["pun",/^-/],["kwd",/^\w+:[\n\r ]/],["pln",/^\w+/]]),["yaml","yml"]); 3 | -------------------------------------------------------------------------------- /static/js/application.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | window.parent.postMessage($(document).height().toString(), "*"); 3 | 4 | /* Hide commit message */ 5 | $("#commit-message").hide(); 6 | 7 | /* Hide/show commit message based on clicks. */ 8 | $('#commit-message-show').click(function() { 9 | $('#commit-message').slideDown('slow'); 10 | }); 11 | $('#commit-message-hide').click(function() { 12 | $('#commit-message').slideUp('slow'); 13 | }); 14 | 15 | }); 16 | -------------------------------------------------------------------------------- /Controllers/Welcome.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | #if PRODUCTION 3 | {-# LANGUAGE Safe #-} 4 | #endif 5 | {-# LANGUAGE FlexibleInstances #-} 6 | {-# LANGUAGE MultiParamTypeClasses #-} 7 | {-# LANGUAGE TypeSynonymInstances #-} 8 | {-# LANGUAGE OverloadedStrings #-} 9 | 10 | module Controllers.Welcome ( welcome ) where 11 | 12 | import Layouts 13 | import Views.Welcome 14 | 15 | import LIO.DCLabel 16 | 17 | import Data.IterIO.Http.Support 18 | 19 | welcome :: Action t b DC () 20 | welcome = renderHtml welcomeView 21 | -------------------------------------------------------------------------------- /static/js/lang-hs.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t-\r ]+/,null,"\t\n \r "],["str",/^"(?:[^\n\f\r"\\]|\\[\S\s])*(?:"|$)/,null,'"'],["str",/^'(?:[^\n\f\r'\\]|\\[^&])'?/,null,"'"],["lit",/^(?:0o[0-7]+|0x[\da-f]+|\d+(?:\.\d+)?(?:e[+-]?\d+)?)/i,null,"0123456789"]],[["com",/^(?:--+[^\n\f\r]*|{-(?:[^-]|-+[^}-])*-})/],["kwd",/^(?:case|class|data|default|deriving|do|else|if|import|in|infix|infixl|infixr|instance|let|module|newtype|of|then|type|where|_)(?=[^\d'A-Za-z]|$)/, 2 | null],["pln",/^(?:[A-Z][\w']*\.)*[A-Za-z][\w']*/],["pun",/^[^\d\t-\r "'A-Za-z]+/]]),["hs"]); 3 | -------------------------------------------------------------------------------- /static/css/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media 2 | screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media 3 | print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px 4 | solid #888}ol.linenums{padding-left:30px; margin-top:0;margin-bottom:0}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 5 | -------------------------------------------------------------------------------- /static/js/lang-lisp.js: -------------------------------------------------------------------------------- 1 | var a=null; 2 | PR.registerLangHandler(PR.createSimpleLexer([["opn",/^\(+/,a,"("],["clo",/^\)+/,a,")"],["com",/^;[^\n\r]*/,a,";"],["pln",/^[\t\n\r \xa0]+/,a,"\t\n\r \xa0"],["str",/^"(?:[^"\\]|\\[\S\s])*(?:"|$)/,a,'"']],[["kwd",/^(?:block|c[ad]+r|catch|con[ds]|def(?:ine|un)|do|eq|eql|equal|equalp|eval-when|flet|format|go|if|labels|lambda|let|load-time-value|locally|macrolet|multiple-value-call|nil|progn|progv|quote|require|return-from|setq|symbol-macrolet|t|tagbody|the|throw|unwind)\b/,a], 3 | ["lit",/^[+-]?(?:[#0]x[\da-f]+|\d+\/\d+|(?:\.\d+|\d+(?:\.\d*)?)(?:[de][+-]?\d+)?)/i],["lit",/^'(?:-*(?:\w|\\[!-~])(?:[\w-]*|\\[!-~])[!=?]?)?/],["pln",/^-*(?:[_a-z]|\\[!-~])(?:[\w-]*|\\[!-~])[!=?]?/i],["pun",/^[^\w\t\n\r "'-);\\\xa0]+/]]),["cl","el","lisp","scm"]); 4 | -------------------------------------------------------------------------------- /Models.hs: -------------------------------------------------------------------------------- 1 | module Models ( CommitObj(..) 2 | , getCommitObj 3 | ) where 4 | 5 | import LIO.DCLabel 6 | import Gitstar.Repo 7 | 8 | -- | Commit object and stats 9 | data CommitObj = CommitObj { commitObj :: GitCommit 10 | -- ^ Git commit object 11 | , commitStats :: GitStats 12 | -- ^ Stats on commit 13 | } deriving (Eq, Show) 14 | 15 | -- | Get commit object 16 | getCommitObj :: Repo -> SHA1 -> DC (Maybe CommitObj) 17 | getCommitObj repo sha = do 18 | mcommit <- getCommit repo sha 19 | mstats <- getStats repo sha 20 | return $ do c <- mcommit 21 | s <- mstats 22 | return CommitObj { commitObj = c, commitStats = s } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This program is free software; you can redistribute it and/or 2 | modify it under the terms of the GNU General Public License as 3 | published by the Free Software Foundation; either version 2, or (at 4 | your option) any later version. 5 | 6 | This program is distributed in the hope that it will be useful, but 7 | WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | General Public License for more details. 10 | 11 | You can obtain copies of permitted licenses from these URLs: 12 | 13 | http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt 14 | http://www.gnu.org/licenses/gpl-3.0.txt 15 | 16 | or by writing to the Free Software Foundation, Inc., 59 Temple Place, 17 | Suite 330, Boston, MA 02111-1307 USA 18 | -------------------------------------------------------------------------------- /Views/Welcome.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | #if PRODUCTION 3 | {-# LANGUAGE Safe #-} 4 | #endif 5 | {-# LANGUAGE FlexibleContexts #-} 6 | {-# LANGUAGE OverloadedStrings #-} 7 | 8 | module Views.Welcome (welcomeView) where 9 | 10 | import Prelude hiding (div, span) 11 | 12 | import Text.Blaze.Html5 hiding (title) 13 | import Text.Blaze.Html5.Attributes hiding (id, label, form, span) 14 | 15 | welcomeView :: Html 16 | welcomeView = do 17 | div ! class_ "hero-unit" $ do 18 | h1 $ "Gitstar Viewer" 19 | p $ toHtml msg 20 | p $ a ! href "/scs/gitstar-viewer" ! class_ "btn btn-large btn-primary" $ 21 | "Browse this project!" 22 | where msg :: String 23 | msg = "A simple app for browsing gitstar git repositories." ++ 24 | " To view project repos point your browser to" ++ 25 | " /user/project/" 26 | -------------------------------------------------------------------------------- /static/js/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /Viewer.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | #if PRODUCTION 3 | {-# LANGUAGE Safe #-} 4 | #endif 5 | module Viewer where 6 | 7 | import Data.Monoid 8 | import Hails.App 9 | import Data.IterIO.Http.Support 10 | import Control.Monad (void) 11 | 12 | import Controllers 13 | 14 | server :: AppReqHandler 15 | server = runAction $ do 16 | req <- getHttpReq 17 | prms0 <- params 18 | body <- getBody >>= (liftLIO . unlabel) 19 | prms1 <- parseParams' req body 20 | void . setParams $ prms1 ++ prms0 21 | runActionRoute $ mconcat 22 | [ routeTop $ routeAction welcome 23 | , routeMethod "GET" $ 24 | routePattern "/:user_name/:project_name/lint/:id" $ 25 | routeAction showLint 26 | , routeMethod "GET" $ 27 | routePattern "/:user_name/:project_name/tree/:id" $ 28 | routeAction showTreeOrCommit 29 | , routeMethod "GET" $ 30 | routePattern "/:user_name/:project_name/blob/:id" $ 31 | routeAction showBlob 32 | , routeMethod "GET" $ 33 | routePattern "/:user_name/:project_name/commit/:id" $ 34 | routeAction showCommit 35 | , routeMethod "GET" $ 36 | routePattern "/:user_name/:project_name" $ 37 | routeAction showBranches 38 | ] 39 | -------------------------------------------------------------------------------- /gitstar-viewer.cabal: -------------------------------------------------------------------------------- 1 | Name: gitstar-gitviewer 2 | Version: 0.1.2 3 | build-type: Simple 4 | License: GPL-2 5 | License-File: LICENSE 6 | Author: HAILS team 7 | Maintainer: Amit Levy , Deian Stefan 8 | Stability: experimental 9 | Synopsis: Simple GitStar code viewer 10 | Category: Web 11 | Cabal-Version: >= 1.6 12 | 13 | Description: 14 | GitStar app that implements a simple repo viewer. 15 | 16 | X-Hails-Server: Viewer 17 | 18 | Library 19 | Build-Depends: base >= 4.5 && < 5, 20 | containers >= 0.4.2 && < 0.5, 21 | bytestring >= 0.9 && < 1, 22 | transformers >= 0.2.2 && < 0.3, 23 | base64-bytestring >= 0.1.1.2 && < 0.2, 24 | filepath >= 1.3 && <2, 25 | lio >= 0.1.3 && < 0.2, 26 | iterIO >= 0.2.2 && < 0.3, 27 | iterio-server >= 0.3.1 && < 0.5, 28 | gitstar-policy >= 0.1.2 && < 1.0, 29 | hails-cjail >= 0.1 && < 1.0, 30 | hails >= 0.1 && < 1.0, 31 | blaze-html == 0.4.3.3 32 | 33 | Ghc-Options: -Wall -fno-warn-orphans 34 | Extensions: Safe 35 | Other-Modules: Viewer 36 | -------------------------------------------------------------------------------- /static/js/lang-n.js: -------------------------------------------------------------------------------- 1 | var a=null; 2 | PR.registerLangHandler(PR.createSimpleLexer([["str",/^(?:'(?:[^\n\r'\\]|\\.)*'|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,a,'"'],["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,a,"#"],["pln",/^\s+/,a," \r\n\t\xa0"]],[["str",/^@"(?:[^"]|"")*(?:"|$)/,a],["str",/^<#[^#>]*(?:#>|$)/,a],["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,a],["com",/^\/\/[^\n\r]*/,a],["com",/^\/\*[\S\s]*?(?:\*\/|$)/, 3 | a],["kwd",/^(?:abstract|and|as|base|catch|class|def|delegate|enum|event|extern|false|finally|fun|implements|interface|internal|is|macro|match|matches|module|mutable|namespace|new|null|out|override|params|partial|private|protected|public|ref|sealed|static|struct|syntax|this|throw|true|try|type|typeof|using|variant|virtual|volatile|when|where|with|assert|assert2|async|break|checked|continue|do|else|ensures|for|foreach|if|late|lock|new|nolate|otherwise|regexp|repeat|requires|return|surroundwith|unchecked|unless|using|while|yield)\b/, 4 | a],["typ",/^(?:array|bool|byte|char|decimal|double|float|int|list|long|object|sbyte|short|string|ulong|uint|ufloat|ulong|ushort|void)\b/,a],["lit",/^@[$_a-z][\w$@]*/i,a],["typ",/^@[A-Z]+[a-z][\w$@]*/,a],["pln",/^'?[$_a-z][\w$@]*/i,a],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,a,"0123456789"],["pun",/^.[^\s\w"-$'./@`]*/,a]]),["n","nemerle"]); 5 | -------------------------------------------------------------------------------- /static/js/flash.js: -------------------------------------------------------------------------------- 1 | /* This is an implementation of error, success, and info alerts 2 | * compatible with bootstrap.js. The current implementation relies on 3 | * cookies and HTML5 session storage. The former is used by the server 4 | * to set a _flash-* key to the message value; the latter is used to 5 | * keep track of which messages have been displayed. The cookie is 6 | * immediately delete, while the session storage item persists until 7 | * the window is closed. */ 8 | 9 | $(function () { 10 | 11 | var flash = function (type, handler) { 12 | var flash_type ='_flash-'+type; 13 | if($.cookie(flash_type)) { 14 | var flash = $.cookie(flash_type).slice(1,-1).split('|'); 15 | var oid = flash[0]; // get unique message id 16 | var msg = flash[1]; // get actual message 17 | if(window.sessionStorage.getItem(oid) == null ) { 18 | window.sessionStorage.setItem(oid, '1'); 19 | $.cookie(flash_type, null); // delete the cookie 20 | $("#flash-messages").append( 21 | '
' 22 | +'×' 23 | + handler(msg) 24 | + '
'); 25 | } 26 | } 27 | } 28 | 29 | flash('error', function (msg){ return 'Error:' + msg; }); 30 | flash('success', function (msg){ return 'Success:' + msg; }); 31 | flash('info', function (msg){ return msg; }); 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /static/js/lang-clj.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2011 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | var a=null; 17 | PR.registerLangHandler(PR.createSimpleLexer([["opn",/^[([{]+/,a,"([{"],["clo",/^[)\]}]+/,a,")]}"],["com",/^;[^\n\r]*/,a,";"],["pln",/^[\t\n\r \xa0]+/,a,"\t\n\r \xa0"],["str",/^"(?:[^"\\]|\\[\S\s])*(?:"|$)/,a,'"']],[["kwd",/^(?:def|if|do|let|quote|var|fn|loop|recur|throw|try|monitor-enter|monitor-exit|defmacro|defn|defn-|macroexpand|macroexpand-1|for|doseq|dosync|dotimes|and|or|when|not|assert|doto|proxy|defstruct|first|rest|cons|defprotocol|deftype|defrecord|reify|defmulti|defmethod|meta|with-meta|ns|in-ns|create-ns|import|intern|refer|alias|namespace|resolve|ref|deref|refset|new|set!|memfn|to-array|into-array|aset|gen-class|reduce|map|filter|find|nil?|empty?|hash-map|hash-set|vec|vector|seq|flatten|reverse|assoc|dissoc|list|list?|disj|get|union|difference|intersection|extend|extend-type|extend-protocol|prn)\b/,a], 18 | ["typ",/^:[\dA-Za-z-]+/]]),["clj"]); 19 | -------------------------------------------------------------------------------- /Layouts.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | #if PRODUCTION 3 | {-# LANGUAGE Safe #-} 4 | #endif 5 | {-# LANGUAGE OverloadedStrings #-} 6 | 7 | module Layouts where 8 | 9 | import LIO 10 | import Gitstar.Policy 11 | import Data.IterIO.Http.Support 12 | 13 | import Control.Monad 14 | 15 | import Hails.App 16 | 17 | import Prelude hiding (head, id, div, span) 18 | import Text.Blaze.Html5 hiding (map) 19 | import Text.Blaze.Html5.Attributes hiding (title, span, content) 20 | import qualified Text.Blaze.Renderer.Utf8 as R (renderHtml) 21 | 22 | renderHtml :: Html -> Action t b DC () 23 | renderHtml htmlBody = do 24 | muName <- getHailsUser 25 | muser <- liftLIO $ maybe (return Nothing) mkUser muName 26 | render "text/html" $ R.renderHtml $ application muser htmlBody 27 | where mkUser uName = Just `liftM` getOrCreateUser uName 28 | 29 | stylesheet :: String -> Html 30 | stylesheet uri = link ! rel "stylesheet" ! type_ "text/css" ! href (toValue uri) 31 | 32 | application :: Maybe User -> Html -> Html 33 | application _ content = docTypeHtml $ do 34 | head $ do 35 | title $ "GitStar - Where loops count" 36 | stylesheet "/static/css/bootstrap.css" 37 | stylesheet "/static/css/application.css" 38 | stylesheet "/static/css/prettify.css" 39 | body ! onload "prettyPrint()" $ do 40 | div ! class_ "row" $ 41 | div ! id "flash-messages" ! class_ "span4 offset4" $ "" 42 | div ! class_ "container" $ content 43 | script ! src "/static/js/jquery.js" $ "" 44 | script ! src "/static/js/jquery.cookie.js" $ "" 45 | script ! src "/static/js/bootstrap.min.js" $ "" 46 | script ! src "/static/js/prettify.js" $ "" 47 | script ! src "/static/js/flash.js" $ "" 48 | script ! src "/static/js/application.js" $ "" 49 | 50 | -------------------------------------------------------------------------------- /Utils.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | #if PRODUCTION 3 | {-# LANGUAGE Safe #-} 4 | #endif 5 | {-# LANGUAGE OverloadedStrings #-} 6 | 7 | module Utils where 8 | 9 | import qualified Data.ByteString.Char8 as S8 10 | import qualified Data.ByteString.Lazy.Char8 as L8 11 | import Data.Maybe (fromJust) 12 | 13 | import Data.IterIO.Http.Support 14 | import Hails.Data.LBson (genObjectId) 15 | 16 | import Control.Monad 17 | import Control.Monad.Trans.State 18 | 19 | import LIO 20 | import LIO.DCLabel 21 | 22 | import Data.IterIO.Http (respAddHeader) 23 | 24 | 25 | -- | Force get parameter value 26 | getParamVal :: Monad m => S8.ByteString -> Action t b m String 27 | getParamVal n = (L8.unpack . paramValue . fromJust) `liftM` param n 28 | 29 | with404orJust :: Monad m => Maybe a -> (a -> Action t b m ()) -> Action t b m () 30 | with404orJust mval act = case mval of 31 | Nothing -> respond404 32 | Just val -> act val 33 | -- 34 | -- Flash notifications 35 | -- 36 | 37 | -- | This sets the @_flash-*@ cookie value to the given message, with 38 | -- a unique message ID. 39 | flash :: String -> String -> Action t b DC () 40 | flash n msg = do 41 | oid <- liftLIO genObjectId 42 | modify $ \s -> 43 | let flashHeader = (S8.pack "Set-Cookie", 44 | S8.pack $ "_flash-" ++ n ++ "=" ++ show (show oid ++ "|" ++ msg)) 45 | in s { actionResp = respAddHeader flashHeader (actionResp s)} 46 | 47 | flashInfo :: String -> Action t b DC () 48 | flashInfo = flash "info" 49 | 50 | flashError :: String -> Action t b DC () 51 | flashError = flash "error" 52 | 53 | flashSuccess :: String -> Action t b DC () 54 | flashSuccess = flash "success" 55 | 56 | safeTail :: [a] -> [a] 57 | safeTail [] = [] 58 | safeTail xs@(_:_) = tail xs 59 | -------------------------------------------------------------------------------- /static/js/jquery.cookie.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Cookie Plugin 3 | * https://github.com/carhartl/jquery-cookie 4 | * 5 | * Copyright 2011, Klaus Hartl 6 | * Dual licensed under the MIT or GPL Version 2 licenses. 7 | * http://www.opensource.org/licenses/mit-license.php 8 | * http://www.opensource.org/licenses/GPL-2.0 9 | */ 10 | (function($) { 11 | $.cookie = function(key, value, options) { 12 | 13 | // key and at least value given, set cookie... 14 | if (arguments.length > 1 && (!/Object/.test(Object.prototype.toString.call(value)) || value === null || value === undefined)) { 15 | options = $.extend({}, options); 16 | 17 | if (value === null || value === undefined) { 18 | options.expires = -1; 19 | } 20 | 21 | if (typeof options.expires === 'number') { 22 | var days = options.expires, t = options.expires = new Date(); 23 | t.setDate(t.getDate() + days); 24 | } 25 | 26 | value = String(value); 27 | 28 | return (document.cookie = [ 29 | encodeURIComponent(key), '=', options.raw ? value : encodeURIComponent(value), 30 | options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE 31 | options.path ? '; path=' + options.path : '', 32 | options.domain ? '; domain=' + options.domain : '', 33 | options.secure ? '; secure' : '' 34 | ].join('')); 35 | } 36 | 37 | // key and possibly options given, get cookie... 38 | options = value || {}; 39 | var decode = options.raw ? function(s) { return s; } : decodeURIComponent; 40 | 41 | var pairs = document.cookie.split('; '); 42 | for (var i = 0, pair; pair = pairs[i] && pairs[i].split('='); i++) { 43 | if (decode(pair[0]) === key) return decode(pair[1] || ''); // IE saves cookies with empty string as "c; ", e.g. without "=" as opposed to EOMB, thus pair[1] may be undefined 44 | } 45 | return null; 46 | }; 47 | })(jQuery); 48 | -------------------------------------------------------------------------------- /static/css/application.css: -------------------------------------------------------------------------------- 1 | @import url(http://fonts.googleapis.com/css?family=Ubuntu:400,700,400italic,700italic|Inika:400,700); 2 | 3 | body { 4 | font-family: "Inika", serif; 5 | } 6 | 7 | p { 8 | font-family: "Ubuntu", sans-serif; 9 | font-size: 110%; 10 | } 11 | 12 | body > .container { 13 | width: 100%; 14 | } 15 | 16 | .nav img { 17 | border: 1px solid white; 18 | height: 22px; 19 | width: 22px; 20 | } 21 | 22 | .alert-commit { 23 | background-color: #f0f8ff; 24 | border-color: #ddd; 25 | border-color: rgba(0, 0, 0, 0.15); 26 | color: #333333; 27 | margin-bottom: 20px; 28 | border: 1px solid #eee; 29 | border: 1px solid rgba(0, 0, 0, 0.05); 30 | -webkit-border-radius: 4px; 31 | -moz-border-radius: 4px; 32 | border-radius: 4px; 33 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); 34 | -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); 35 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); 36 | } 37 | 38 | .nav-middle-header { 39 | display: block; 40 | margin-top: 6px; 41 | padding: 3px 15px; 42 | font-size: 11px; 43 | font-weight: bold; 44 | line-height: 18px; 45 | color: #999999; 46 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 47 | text-transform: uppercase; 48 | margin-left: -15px; 49 | margin-right: -15px; 50 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 51 | } 52 | 53 | #commit-message { 54 | overflow: hidden; 55 | text-overflow: ellipsis; 56 | } 57 | 58 | .commit-info { 59 | text-align: right; 60 | font-size: 11px; 61 | } 62 | .sha { 63 | font-family: Menlo, Monaco, "Courier New", monospace; 64 | } 65 | .curved { 66 | background-color: #fbfbfb; 67 | background-image: -moz-linear-gradient(top, #ffffff, #f5f5f5); 68 | background-image: -ms-linear-gradient(top, #ffffff, #f5f5f5); 69 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f5f5f5)); 70 | background-image: -webkit-linear-gradient(top, #ffffff, #f5f5f5); 71 | background-image: -o-linear-gradient(top, #ffffff, #f5f5f5); 72 | background-image: linear-gradient(top, #ffffff, #f5f5f5); 73 | background-repeat: repeat-x; 74 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0); 75 | -webkit-border-radius: 3px; 76 | -moz-border-radius: 3px; 77 | border-radius: 3px; 78 | -webkit-box-shadow: inset 0 1px 0 #ffffff; 79 | -moz-box-shadow: inset 0 1px 0 #ffffff; 80 | box-shadow: inset 0 1px 0 #ffffff; 81 | } 82 | 83 | .diff-title { 84 | padding: 7px 14px; 85 | margin: 0 0 2px; 86 | border: 1px solid #ddd; 87 | } 88 | 89 | .diff-show-file { 90 | float:right; 91 | } 92 | .diff-content { 93 | margin: 0 0 25px; 94 | } 95 | 96 | .diff-remove { background-color: rgb(255,221,221); } 97 | .diff-insert { background-color: rgb(221,255,221); } 98 | .diff-hunk { background-color: rgb(234,242,245); } 99 | .diff-common { } 100 | 101 | .diff-lineno { 102 | color: rgb(110,110,110); 103 | text-align: right; 104 | padding-left:30px; 105 | background-color: #eee !important; 106 | border-right: 1px solid #dddddd; 107 | } 108 | 109 | .diff-remove:hover, 110 | .diff-insert:hover, 111 | .diff-hunk:hover, 112 | .diff-common:hover { 113 | background-color: rgb(255,248,194); 114 | } 115 | 116 | .col-content { width: 100%; } 117 | 118 | .lint { 119 | float:right; 120 | } 121 | -------------------------------------------------------------------------------- /Data/UniDiff.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | #if PRODUCTION 3 | {-# LANGUAGE Safe #-} 4 | #endif 5 | {-# LANGUAGE OverloadedStrings #-} 6 | -- | Simple unified format diff parser 7 | module Data.UniDiff ( LineType(..) 8 | , DiffLine(..) 9 | , parseDiff 10 | ) where 11 | 12 | import Data.Char (isSpace) 13 | import qualified Data.ByteString.Char8 as S8 14 | 15 | type S = S8.ByteString 16 | 17 | -- | A range contains a line number and count. 18 | data Range = Range { lineNo :: !Int 19 | , lineCount :: !Int 20 | } deriving (Eq, Show) 21 | 22 | -- | Line type 23 | data LineType = Remove !Int 24 | | Insert !Int 25 | | Common !Int 26 | | Hunk 27 | deriving Eq 28 | 29 | instance Show LineType where 30 | show (Remove i) = show i 31 | show (Insert i) = show i 32 | show (Common i) = show i 33 | show Hunk = "..." 34 | 35 | -- | A diff line 36 | data DiffLine = DiffLine { dlineType :: LineType 37 | , dlineCont :: S 38 | } deriving (Eq, Show) 39 | 40 | 41 | -- 42 | -- 43 | -- 44 | 45 | -- | Given current (remove, insert) line numbers and line content 46 | -- return the new line numbers and \"parsed\" line. 47 | s8ToLine :: (Int, Int) -> S -> (Int, Int, DiffLine) 48 | s8ToLine (r,i) l = case () of 49 | _ | isRemove l -> (r+1,i , DiffLine { dlineType = Remove r, dlineCont = l }) 50 | _ | isInsert l -> (r ,i+1, DiffLine { dlineType = Insert i, dlineCont = l }) 51 | _ | isHunk l -> (r ,i , DiffLine { dlineType = Hunk , dlineCont = l }) 52 | _ -> (r+1,i+1, DiffLine { dlineType = Common (max r i) 53 | , dlineCont = l }) 54 | 55 | -- | Parse all lines of a file 56 | parseLines :: (Int, Int) -> [S] -> Maybe [DiffLine] 57 | parseLines _ [] = return [] 58 | parseLines ri (s:ss) = 59 | let (r, i, l) = s8ToLine ri s 60 | in do riNew <- if dlineType l == Hunk 61 | then do (rangeR, rangeI) <- parseRanges $ dlineCont l 62 | return (lineNo rangeR, lineNo rangeI) 63 | else return (r,i) 64 | dls <- parseLines riNew ss 65 | return (l:dls) 66 | 67 | -- | Parse a file 68 | parseDiff :: S -> Maybe [DiffLine] 69 | parseDiff file = let ls = dropWhile (not . isHunk) $ S8.lines file 70 | in parseLines (0,0) ls 71 | 72 | -- 73 | -- 74 | 75 | -- | Check remove 76 | isRemove :: S -> Bool 77 | isRemove = (== "-") . (S8.take 1) 78 | 79 | -- | Check insert 80 | isInsert :: S -> Bool 81 | isInsert = (== "+") . (S8.take 1) 82 | 83 | -- | Check for start of hunk 84 | isHunk :: S -> Bool 85 | isHunk = (== "@@") . (S8.take 2) 86 | 87 | -- Parses change chunk of the form: 88 | -- 89 | -- > @@ l1[,s1] +l2[,s2] @@ optional section heading 90 | -- 91 | parseRanges :: S -> Maybe (Range, Range) 92 | parseRanges line | not (isHunk line) = Nothing 93 | | otherwise = 94 | let line1 = safeTail $ S8.dropWhile (/='-') line 95 | (l1,line2) = S8.span (\c -> (not (isSpace c)) && c /= ',') line1 96 | (s1,line3) = 97 | if safeHead line2 == ',' 98 | then S8.span (\c -> not (isSpace c) && c /= '+') $ safeTail line2 99 | else ("1",safeTail line2) 100 | line4 = S8.dropWhile (\c -> isSpace c || c == '+') line3 101 | (l2,line5) = S8.span (\c -> not (isSpace c) && c `notElem` [',','@']) line4 102 | s2 = if safeHead line5 == ',' 103 | then S8.takeWhile (\c -> not (isSpace c) && c /= '@') $ safeTail line5 104 | else "1" 105 | in do r1 <- readRange l1 s1 106 | r2 <- readRange l2 s2 107 | return (r1, r2) 108 | where safeHead s = if S8.null s then '\0' else S8.head s 109 | safeTail s = if S8.null s then S8.empty else S8.tail s 110 | readRange lS sS = do 111 | l <- maybeRead $ S8.unpack lS 112 | s <- maybeRead $ S8.unpack sS 113 | return Range { lineNo = l, lineCount = s} 114 | 115 | maybeRead :: Read a => String -> Maybe a 116 | maybeRead str = case reads str of 117 | [(x, "")] -> Just x 118 | _ -> Nothing 119 | 120 | -------------------------------------------------------------------------------- /Controllers/Repo.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | #if PRODUCTION 3 | {-# LANGUAGE Safe #-} 4 | #endif 5 | {-# LANGUAGE FlexibleInstances #-} 6 | {-# LANGUAGE MultiParamTypeClasses #-} 7 | {-# LANGUAGE TypeSynonymInstances #-} 8 | {-# LANGUAGE OverloadedStrings #-} 9 | 10 | module Controllers.Repo ( showTreeOrCommit 11 | , showBlob 12 | , showBranches 13 | , showCommit 14 | -- 15 | , showLint 16 | ) where 17 | 18 | import Models 19 | import Layouts 20 | import Views.Repo 21 | import Utils 22 | 23 | import LIO 24 | import LIO.MonadCatch 25 | import LIO.Handle 26 | import LIO.DCLabel 27 | 28 | import Hails.CJail 29 | 30 | import Gitstar.Repo 31 | 32 | import Control.Monad 33 | 34 | import qualified Data.List as List 35 | import Data.Maybe 36 | import Data.IterIO.Http 37 | import Data.IterIO.Http.Support 38 | import qualified Data.ByteString.Base64 as B64 39 | import qualified Data.ByteString.Char8 as S8 40 | 41 | import System.FilePath (splitDirectories, takeExtension) 42 | 43 | -- | Redirect to show master branch 44 | showBranches :: Action t b DC () 45 | showBranches = do 46 | uName <- getParamVal "user_name" 47 | pName <- getParamVal "project_name" 48 | let repo = Repo { repoOwner = uName, repoName = pName } 49 | mbranches <- liftLIO $ getBranches repo 50 | redirectToMasterOrSingle repo mbranches 51 | where redirectToMasterOrSingle repo mbs = 52 | case mbs of 53 | Just bs | "master" `elem` map fst bs -> 54 | redirectToRepo repo "tree/master" 55 | Just [(b,_)] -> redirectToRepo repo $ "tree/" ++ b 56 | _ -> renderHtml $ viewMBranches repo mbs 57 | redirectToRepo r s = redirectTo $ repo2url r ++ "/" ++ s 58 | 59 | 60 | -- 61 | -- Tree / commit tree 62 | -- 63 | 64 | -- | Find the branch/tree and and diplay the corresponding 65 | -- commit/tree. 66 | showTreeOrCommit :: Action t b DC () 67 | showTreeOrCommit = do 68 | uName <- getParamVal "user_name" 69 | pName <- getParamVal "project_name" 70 | bName <- getParamVal "id" -- tree sha or branch name 71 | req <- getHttpReq 72 | let path = S8.unpack . reqPath $ req 73 | dirs = drop 3 $ splitDirectories' path -- rm /usr/proj/tree 74 | repo = Repo { repoOwner = uName, repoName = pName } 75 | mbranches <- liftLIO $ getBranches repo 76 | with404orJust mbranches $ \bs -> 77 | if null bs 78 | then respond404 79 | else case List.lookup bName bs of 80 | Just sha -> treeCommitShow repo sha dirs 81 | _ -> treeShow repo (SHA1 bName) dirs Nothing 82 | 83 | 84 | -- | Given a title and refernce to the commit, find the commit object 85 | -- and tree it points to, and show them. 86 | treeCommitShow :: Repo -> SHA1 -> [String] -> Action t b DC () 87 | treeCommitShow repo sha dirs = do 88 | mcommit <- liftLIO $ getCommitObj repo sha 89 | with404orJust mcommit $ \commit -> 90 | treeShow repo (cmtTree . commitObj $ commit) dirs (Just commit) 91 | 92 | -- | Show a tree 93 | treeShow :: Repo -> SHA1 -> [String] -> Maybe CommitObj -> Action t b DC () 94 | treeShow repo sha dirs mcommit = do 95 | mtree <- liftLIO $ getTree repo sha 96 | with404orJust mtree $ \tree -> do 97 | mt <- liftLIO $ getTreeByPath repo tree $ tail dirs 98 | with404orJust mt $ \t -> 99 | renderHtml $ viewTreeOrCommit repo sha (if length dirs == 1 100 | then mcommit 101 | else Nothing) t dirs 102 | 103 | -- | Find the tree given a head tree object and remaining tree names 104 | getTreeByPath :: Repo -> GitTree -> [String] -> DC (Maybe GitTree) 105 | getTreeByPath repo headTree dirs = 106 | if null dirs 107 | then return $ Just headTree 108 | else do let n = head dirs 109 | newPath = safeTail dirs 110 | mEnt = listToMaybe $ filter ((==n) . entPath) headTree 111 | case mEnt of 112 | Nothing -> return Nothing 113 | Just ent -> do mtree <- getTree repo (entPtr ent) 114 | case mtree of 115 | Nothing -> return Nothing 116 | Just tree -> getTreeByPath repo tree newPath 117 | 118 | 119 | -- 120 | -- Commit objects 121 | -- 122 | 123 | showCommit :: Action t b DC () 124 | showCommit = do 125 | uName <- getParamVal "user_name" 126 | pName <- getParamVal "project_name" 127 | sha <- SHA1 `liftM` getParamVal "id" 128 | let repo = Repo { repoOwner = uName, repoName = pName } 129 | mcommit <- liftLIO $ getCommitObj repo sha 130 | mdiffs <- liftLIO $ getDiffs repo sha 131 | let mcd = mcommit >>= \mc -> mdiffs >>= \md -> return (mc,md) 132 | with404orJust mcd $ \(commit, diffs) -> 133 | renderHtml $ viewCommit repo commit diffs 134 | 135 | 136 | 137 | -- 138 | -- Blobs 139 | -- 140 | 141 | -- | Generic blob show (view function may perform some filtering) 142 | showBlobGen :: (Repo -> SHA1 -> GitBlob -> [FilePath] -> Action t b DC ()) 143 | -> Action t b DC () 144 | showBlobGen viewBlobFunc = do 145 | uName <- getParamVal "user_name" 146 | pName <- getParamVal "project_name" 147 | bName <- getParamVal "id" -- tree sha or branch name 148 | req <- getHttpReq 149 | let path = S8.unpack . reqPath $ req 150 | dirs = drop 3 $ splitDirectories' path -- rm /usr/proj/blob 151 | objName = last dirs 152 | repo = Repo { repoOwner = uName, repoName = pName } 153 | mbranches <- liftLIO $ getBranches repo 154 | with404orJust mbranches $ \bs -> 155 | if null bs 156 | then respond404 157 | else do let sha = fromMaybe (SHA1 bName) $ List.lookup bName bs 158 | mtree <- liftLIO $ getTree repo sha 159 | with404orJust mtree $ \tree -> do 160 | mt <- liftLIO $ getTreeByPath repo tree (init . tail $ dirs) 161 | with404orJust mt $ \t -> 162 | let mEnt = listToMaybe $ filter ((==objName) . entPath) t 163 | in with404orJust mEnt $ \ent -> do 164 | mblob <- liftLIO $ getBlob repo (entPtr ent) 165 | with404orJust mblob $ \blob -> 166 | viewBlobFunc repo sha blob dirs 167 | 168 | showBlob :: Action t b DC () 169 | showBlob = showBlobGen (\repo sha blob dirs -> 170 | renderHtml $ viewBlob repo sha blob dirs) 171 | 172 | showLint :: Action t b DC () 173 | showLint = showBlobGen (\repo sha blob dirs -> do 174 | let fname = last dirs 175 | lint = case takeExtension fname of 176 | ".hs" -> "hlint" 177 | _ -> "splint" 178 | out <- liftLIO $ inCJail' $ do 179 | lph <- createProcess (shell $ "cd /tmp; cat > " ++ fname ++ 180 | " && " ++ lint ++ " " ++ fname) 181 | liftLIO $ hPut (stdIn lph) (B64.decodeLenient $ blobContent blob) 182 | liftLIO $ hClose (stdIn lph) 183 | resErr <- liftLIO $ hGetContents (stdErr lph) 184 | resOut <- liftLIO $ hGetContents (stdOut lph) 185 | return $ if S8.null resOut then resErr else resOut 186 | renderHtml $ viewFilteredBlob repo sha blob (lint ++ " output:") out dirs) 187 | where inCJail' act = (inCJail act) `onException` 188 | return ("Execution failed" :: String) 189 | 190 | 191 | -- 192 | -- Misc 193 | -- 194 | 195 | -- | Same as 'splitdirectories', but does not keep the first slash. 196 | splitDirectories' :: FilePath -> [FilePath] 197 | splitDirectories' p = let ds = splitDirectories p 198 | in case ds of 199 | ("/":xs) -> xs 200 | xs -> xs 201 | -------------------------------------------------------------------------------- /static/js/prettify.js: -------------------------------------------------------------------------------- 1 | var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; 2 | (function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= 3 | [],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), 9 | l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, 10 | q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, 11 | q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, 12 | "");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), 13 | a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} 14 | for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], 18 | "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], 19 | H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], 20 | J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ 21 | I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), 22 | ["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", 23 | /^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), 24 | ["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", 25 | hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= 26 | !k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p Maybe [(String, SHA1)] -> Html 41 | viewMBranches repo mbranches = do 42 | h2 . toHtml $ "Browse " ++ repo2url repo 43 | case mbranches of 44 | Just branches | not (null branches) -> do 45 | let bNames = map fst branches 46 | master = if "master" `elem` bNames 47 | then "master" 48 | else List.head bNames 49 | rest = List.delete master bNames 50 | ul ! class_ "nav nav-pills" $ do 51 | li ! class_ "nav-middle-header" $ "View branch:" 52 | li ! class_ "dropdown" $ do 53 | a ! class_ "dropdown-toggle" ! dataAttribute "toggle" "dropdown" 54 | ! href "#" $ do 55 | toHtml master 56 | span ! class_ "caret" $ "" 57 | ul ! class_ "dropdown-menu" $ 58 | forM_ (master:rest) $ \branch -> 59 | li $ a ! href (branchUrl branch) $ toHtml branch 60 | _ -> p . toHtml $ defaultMsg 61 | where defaultMsg :: String 62 | defaultMsg = "Project doesn't seem to have any branches." ++ 63 | " Perhaps you should push to a branch such as \'master\'." 64 | branchUrl branch = toValue $ repo2url repo ++ "/tree/" ++ branch 65 | 66 | -- | Show a commit or tree. 67 | viewTreeOrCommit :: Repo 68 | -> SHA1 69 | -> Maybe CommitObj 70 | -> GitTree 71 | -> [String] 72 | -> Html 73 | viewTreeOrCommit repo _ mcommit tree dirs = do 74 | let title = List.head dirs -- branch name 75 | -- (name, path) pairs for building breadcrumb links 76 | links = foldl (\paths d -> paths ++ [(d, (snd . List.last) paths ++ "/" ++ d)]) 77 | [(title, title)] (safeTail dirs) 78 | -- tree path 79 | path = snd . last $ links 80 | ul ! class_"breadcrumb" $ do 81 | li $ do a ! href (toValue $ repo2url repo) $ 82 | span ! class_ "icon-home" $ " " 83 | span ! class_ "divider" $ "/" 84 | forM_ links $ \(n,lnk) -> 85 | li $ do a ! href (toValue $ repo2url repo ++ "/tree/" ++ lnk) $ toHtml n 86 | span ! class_ "divider" $ "/" 87 | -- Generate comit box, if any: 88 | maybe (return ()) (htmlCommitBox repo) mcommit 89 | table ! class_ "table table-bordered table-condensed" $ do 90 | colgroup $ do 91 | col 92 | col ! class_ "span2" 93 | thead ! class_ "curved" $ tr $ do th "name" 94 | th "size" 95 | tbody $ forM_ tree $ \ent -> 96 | tr $ do th $ htmlTreeEntry repo ent path 97 | th $ maybe "--" (toHtml . showSize) $ entSize ent 98 | where showSize x = let dkb = truncate $ (toRational x) / 1024 * 100 :: Int 99 | kb = fromRational $ toRational dkb / 100 :: Double 100 | in show kb ++ " KB" 101 | 102 | -- | Show a commit object 103 | viewCommit :: Repo -> CommitObj -> [GitDiff] -> Html 104 | viewCommit repo cmtObj diffs = do 105 | -- Show commit info 106 | htmlCommitBox repo cmtObj 107 | -- Show file changes 108 | let stats = commitStats cmtObj 109 | let nrs = [1..] :: [Int] 110 | table ! class_ "table" $ do 111 | colgroup $ do 112 | col ! class_ "span1" 113 | col 114 | col ! class_ "span2" 115 | col ! class_ "span2" 116 | forM_ (zip diffs nrs) $ \(diff,nr) -> do 117 | let path = dpathName . diffPath $ diff 118 | mstat = getFileStat stats path 119 | tr $ do 120 | td $ case dpathChanges (diffPath diff) of 121 | Just NewFile -> span ! class_ "icon-plus-sign" $ "" 122 | Just DeletedFile -> span ! class_ "icon-minus-sign" $ "" 123 | _ -> span ! class_ "icon-adjust" $ "" 124 | td $ a ! href (toValue $ "#diff-" ++ show nr) $ toHtml path 125 | case mstat of 126 | Nothing -> do 127 | td "" 128 | td "" 129 | Just stat -> do 130 | td $ toHtml $ pluralize (fstatAdditions stat) "addition" 131 | td $ toHtml $ pluralize (fstatDeletions stat) "deletion" 132 | -- Show individual file diffs 133 | let commit = commitObj cmtObj 134 | forM_ (zip diffs nrs) $ \(diff, nr) -> do 135 | let path = dpathName . diffPath $ diff 136 | div ! id (toValue $ "diff-" ++ show nr) $ do 137 | div ! class_ "diff-title curved" $ do 138 | span $ toHtml path 139 | span ! class_ "diff-show-file" $ 140 | let shaS = show (cmtPtr commit) 141 | in if (dpathChanges . diffPath $ diff) == Just DeletedFile 142 | then "" 143 | else a ! class_ "sha" 144 | ! href (toValue $ repo2url repo ++ "/blob/" 145 | ++ shaS ++ "/" ++ path) $ 146 | toHtml $ "View file @" ++ take 6 shaS 147 | div ! class_ "diff-content" $ pre $ 148 | let diffFile = B64.decodeLenient $ diffContent diff 149 | in fromMaybe (rawDiff diffFile) $ diffToHtml diffFile 150 | 151 | where getFileStat stats path = 152 | -- fail is datastructure mismatch 153 | List.find ((==path) . fstatPath) $ statFiles stats 154 | rawDiff file = 155 | let ls = lines $ S8.unpack file 156 | in toHtml . unlines . safeTail $ ls 157 | 158 | diffToHtml :: S8.ByteString -> Maybe Html 159 | diffToHtml file = do 160 | ls <- parseDiff file 161 | return $ 162 | table ! class_ "table-striped table-bordered" $ do 163 | colgroup $ do 164 | col 165 | col 166 | col ! class_ "col-content" 167 | forM_ ls $ \l -> tr $ do 168 | let ltype = dlineType l 169 | td' x = td ! class_ "diff-lineno" $ x 170 | case ltype of 171 | (Remove x) -> td' (toHtml $ show x) >> td' " " 172 | (Insert x) -> td' " " >> td' (toHtml $ show x) 173 | (Common x) -> td' (toHtml $ show x) >> td' (toHtml $ show x) 174 | _ -> td' "..." >> td' "..." 175 | td $ div ! class_ (typeToClass ltype) $ do 176 | toHtml . S8.unpack $ dlineCont l 177 | where typeToClass t = case t of 178 | Remove _ -> "diff-remove" 179 | Insert _ -> "diff-insert" 180 | Common _ -> "diff-common" 181 | Hunk -> "diff-hunk" 182 | 183 | -- | Show a blob 184 | viewBlob :: Repo 185 | -> SHA1 186 | -> GitBlob 187 | -> [String] 188 | -> Html 189 | viewBlob repo _ blob dirs = do 190 | let title = List.head dirs -- branch name 191 | -- (name, path) pairs for building breadcrumb links 192 | links' = foldl (\paths d -> paths ++ [(d, (snd . List.last) paths ++ "/" ++ d)]) 193 | [(title, title)] (safeTail dirs) 194 | (objName, objPath) = last links' 195 | links = init links' 196 | ul ! class_"breadcrumb" $ do 197 | li $ do a ! href (toValue $ repo2url repo) $ span ! class_ "icon-home" $ " " 198 | span ! class_ "divider" $ "/" 199 | forM_ links $ \(n,lnk) -> 200 | li $ do a ! href (toValue $ repo2url repo ++ "/tree/" ++ lnk) $ toHtml n 201 | span ! class_ "divider" $ "/" 202 | li $ a ! href (toValue $ repo2url repo ++ "/blob/" ++ objPath) $ 203 | toHtml objName 204 | when (takeExtension objName `elem` [".c", ".hs"]) $ 205 | a ! href (toValue $ repo2url repo ++ "/lint/" ++ List.intercalate "/" dirs) 206 | ! class_ "lint" $ do 207 | span ! class_ "icon-question-sign white" $ "" 208 | " Lint" 209 | div $ pre ! class_ "prettyprint linenums" $ 210 | toHtml . S8.unpack . B64.decodeLenient $ blobContent blob 211 | 212 | -- | Show a filtered blob 213 | viewFilteredBlob :: Repo 214 | -> SHA1 215 | -> GitBlob 216 | -> String 217 | -> S8.ByteString 218 | -> [String] 219 | -> Html 220 | viewFilteredBlob repo _ blob filterTitle fblob dirs = do 221 | let title = List.head dirs -- branch name 222 | -- (name, path) pairs for building breadcrumb links 223 | links' = foldl (\paths d -> paths ++ [(d, (snd . List.last) paths ++ "/" ++ d)]) 224 | [(title, title)] (safeTail dirs) 225 | (objName, objPath) = last links' 226 | links = init links' 227 | ul ! class_"breadcrumb" $ do 228 | li $ do a ! href (toValue $ repo2url repo) $ span ! class_ "icon-home" $ " " 229 | span ! class_ "divider" $ "/" 230 | forM_ links $ \(n,lnk) -> 231 | li $ do a ! href (toValue $ repo2url repo ++ "/tree/" ++ lnk) $ toHtml n 232 | span ! class_ "divider" $ "/" 233 | li $ a ! href (toValue $ repo2url repo ++ "/blob/" ++ objPath) $ 234 | toHtml objName 235 | div $ pre ! class_ "prettyprint linenums" $ 236 | toHtml . S8.unpack . B64.decodeLenient $ blobContent blob 237 | div $ do h3 $ toHtml filterTitle 238 | pre $ toHtml . S8.unpack $ fblob 239 | 240 | 241 | -- | Create: entry-icon entry-path 242 | htmlTreeEntry :: Repo -> GitTreeEntry -> String -> Html 243 | htmlTreeEntry repo ent branch = do 244 | span ! class_ (entIcon ent) $ " " 245 | void " " 246 | a ! href (toValue $ repo2url repo ++ "/" ++ entObjType ent 247 | ++ "/" ++ branch 248 | ++ "/" ++ entPath ent) $ 249 | toHtml $ entPath ent 250 | 251 | -- | Getpath prefix for a tree entry 252 | entObjType :: GitTreeEntry -> String 253 | entObjType ent = case entType ent of 254 | GtTree -> "tree" 255 | GtBlob -> "blob" 256 | GtTag -> "tag" 257 | GtCommit -> "commit" 258 | 259 | -- | Convert tree entry type to icon 260 | entIcon :: GitTreeEntry -> AttributeValue 261 | entIcon ent = case entType ent of 262 | GtTree -> "icon-folder-open" 263 | GtBlob -> "icon-file" 264 | GtTag -> "icon-tag" 265 | GtCommit -> "icon-flag" 266 | 267 | -- | Generate alert-box with commit message and stats 268 | htmlCommitBox :: Repo -> CommitObj -> Html 269 | htmlCommitBox repo cmtObj = do 270 | let commit = commitObj cmtObj 271 | stats = commitStats cmtObj 272 | div ! class_ "alert fade in alert-commit" $ do 273 | a ! class_ "close" ! 274 | dataAttribute "dismiss" "alert" $ preEscapedString "×" 275 | div ! class_ "row-fluid" $ do 276 | let author = cmtAuthor commit 277 | message = lines . S8.unpack . cmtMessage $ commit 278 | div ! class_ "span8" $ do 279 | h4 ! class_ "alert-heading" $ do 280 | span ! class_ "icon-comment" $ " " 281 | toHtml $ " " ++ safeHead "" message 282 | blockquote $ do 283 | div ! id "commit-message" $ 284 | forM_ (rmFrontWS . safeTail $ message) $ \l -> toHtml l >> br 285 | a ! id "commit-message-show" ! href "#" $ 286 | span ! class_ "icon-chevron-down" $ "" 287 | void " " 288 | a ! id "commit-message-hide" ! href "#" $ 289 | span ! class_ "icon-chevron-up" $ "" 290 | p . em . small . toHtml $ "Authored " ++ (S8.unpack $ authDate author) 291 | ++ " by " ++ (S8.unpack $ authName author) 292 | ++ " <" ++ (S8.unpack $ authEmail author) 293 | ++ "> " 294 | div ! class_ "span4 commit-info" $ do 295 | div $ showSha True (cmtPtr commit) 296 | p $ do 297 | toHtml $ pluralize (length . statFiles $ stats) "file" 298 | ++ " changed, with " 299 | ++ pluralize (statAdditions stats) "addition" 300 | ++ " and " 301 | ++ pluralize (statDeletions stats) "deletion" 302 | ++ "." 303 | br 304 | let parents = cmtParents commit 305 | toHtml ("parent" ++ if length parents /= 1 306 | then "s: " else ": " :: String) 307 | forM_ parents $ \sha -> showSha False sha >> " " 308 | where rmFrontWS = dropWhile (all isSpace) 309 | showSha isCommit sha = 310 | let shaS = show sha 311 | classes :: String 312 | classes = "sha" ++ if isCommit then " label" else "" 313 | in a ! class_ (toValue classes) 314 | ! href (toValue $ repo2url repo ++ "/commit/" ++ shaS) 315 | $ toHtml (take 6 shaS) 316 | -- 317 | -- Misc 318 | -- 319 | 320 | -- | Print a number and pluralize suffix 321 | pluralize :: Int -> String -> String 322 | pluralize n s = show n ++ " " ++ (if n == 1 then s else s++"s") 323 | 324 | repo2url :: Repo -> String 325 | repo2url r = "/" ++ repoOwner r ++ "/" ++ repoName r 326 | 327 | safeHead :: a -> [a] -> a 328 | safeHead def ls = fromMaybe def $ listToMaybe ls 329 | -------------------------------------------------------------------------------- /static/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Bootstrap.js by @fat & @mdo 3 | * plugins: bootstrap-transition.js, bootstrap-modal.js, bootstrap-dropdown.js, bootstrap-scrollspy.js, bootstrap-tab.js, bootstrap-tooltip.js, bootstrap-popover.js, bootstrap-alert.js, bootstrap-button.js, bootstrap-collapse.js, bootstrap-carousel.js, bootstrap-typeahead.js 4 | * Copyright 2012 Twitter, Inc. 5 | * http://www.apache.org/licenses/LICENSE-2.0.txt 6 | */ 7 | !function(a){a(function(){a.support.transition=function(){var b=document.body||document.documentElement,c=b.style,d=c.transition!==undefined||c.WebkitTransition!==undefined||c.MozTransition!==undefined||c.MsTransition!==undefined||c.OTransition!==undefined;return d&&{end:function(){var b="TransitionEnd";return a.browser.webkit?b="webkitTransitionEnd":a.browser.mozilla?b="transitionend":a.browser.opera&&(b="oTransitionEnd"),b}()}}()})}(window.jQuery),!function(a){function c(){var b=this,c=setTimeout(function(){b.$element.off(a.support.transition.end),d.call(b)},500);this.$element.one(a.support.transition.end,function(){clearTimeout(c),d.call(b)})}function d(a){this.$element.hide().trigger("hidden"),e.call(this)}function e(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;this.$backdrop=a('