├── data ├── templates │ ├── listitem.st │ ├── markuphelp.st │ ├── messages.st │ ├── footer.st │ ├── logo.st │ ├── content.st │ ├── expire.st │ ├── userbox.st │ ├── getuser.st │ ├── pagetools.st │ ├── sitenav.st │ └── page.st ├── static │ ├── robots.txt │ ├── img │ │ ├── logo.png │ │ ├── icons │ │ │ ├── feed.png │ │ │ ├── page.png │ │ │ └── folder.png │ │ └── lambda-bann.png │ ├── js │ │ ├── uploadForm.js │ │ ├── preview.js │ │ ├── dragdiff.js │ │ ├── search.js │ │ ├── MathMLinHTML.js │ │ └── jquery.hotkeys-0.7.9.min.js │ └── css │ │ ├── custom.css │ │ ├── hk-pyg.css │ │ ├── ie.css │ │ ├── print.css │ │ ├── reset-fonts-grids.css │ │ └── screen.css ├── Paths_gitit.hs ├── FrontPage.page ├── markupHelp │ ├── RST │ ├── Markdown │ ├── RST+LHS │ ├── HTML │ ├── LaTeX │ ├── Markdown+LHS │ └── LaTeX+LHS ├── markup.HTML ├── markup.RST ├── markup.LaTeX ├── post-update ├── markup.Markdown ├── Help.page └── default.conf ├── .gitmodules ├── Setup.lhs ├── plugins ├── Deprofanizer.hs ├── ShowUser.hs ├── CapitalizeEmphasis.hs ├── Signature.hs ├── PigLatin.hs ├── Subst.hs ├── Dot.hs ├── ImgTex.hs └── WebArchiver.hs ├── TODO ├── YUI-LICENSE ├── HCAR-gitit.tex ├── expireGititCache.hs ├── Network ├── Gitit │ ├── Server.hs │ ├── Cache.hs │ ├── Plugins.hs │ ├── Util.hs │ ├── State.hs │ ├── Feed.hs │ ├── Page.hs │ ├── Layout.hs │ ├── Interface.hs │ ├── Initialize.hs │ ├── Config.hs │ └── Export.hs └── Gitit.hs ├── RELANN-0.6.1 ├── gitit.hs ├── gitit.cabal └── TANGOICONS /data/templates/listitem.st: -------------------------------------------------------------------------------- 1 |
  • $it$
  • 2 | -------------------------------------------------------------------------------- /data/static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /_ 3 | -------------------------------------------------------------------------------- /data/templates/markuphelp.st: -------------------------------------------------------------------------------- 1 |
    2 | $markuphelp$ 3 |
    4 | -------------------------------------------------------------------------------- /data/templates/messages.st: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "website"] 2 | path = website 3 | url = git@github.com:jgm/gitit.git 4 | -------------------------------------------------------------------------------- /Setup.lhs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env runhaskell 2 | > import Distribution.Simple 3 | > main = defaultMain 4 | -------------------------------------------------------------------------------- /data/static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/gitit/master/data/static/img/logo.png -------------------------------------------------------------------------------- /data/static/img/icons/feed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/gitit/master/data/static/img/icons/feed.png -------------------------------------------------------------------------------- /data/static/img/icons/page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/gitit/master/data/static/img/icons/page.png -------------------------------------------------------------------------------- /data/static/img/icons/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/gitit/master/data/static/img/icons/folder.png -------------------------------------------------------------------------------- /data/static/img/lambda-bann.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/gitit/master/data/static/img/lambda-bann.png -------------------------------------------------------------------------------- /data/templates/footer.st: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /data/Paths_gitit.hs: -------------------------------------------------------------------------------- 1 | module Paths_gitit where 2 | 3 | getDataFileName :: FilePath -> IO FilePath 4 | getDataFileName = return 5 | -------------------------------------------------------------------------------- /data/static/js/uploadForm.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | $("#file").change(function () { $("#wikiname").val($(this).val()); }); 3 | }); 4 | -------------------------------------------------------------------------------- /data/templates/logo.st: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /data/static/css/custom.css: -------------------------------------------------------------------------------- 1 | @import url("screen.css"); /* default gitit screen styles */ 2 | @import url("hk-pyg.css"); /* for syntax highlighting */ 3 | 4 | /* Put your custom style modifications here: */ 5 | 6 | -------------------------------------------------------------------------------- /data/templates/content.st: -------------------------------------------------------------------------------- 1 |
    2 | $if(revision)$ 3 |

    Revision $revision$

    4 | $endif$ 5 |

    $pagetitle$

    6 | $if(messages)$ 7 | $messages()$ 8 | $endif$ 9 | $content$ 10 |
    11 | -------------------------------------------------------------------------------- /data/templates/expire.st: -------------------------------------------------------------------------------- 1 | $if(usecache)$ 2 | 3 | 11 | $endif$ 12 | -------------------------------------------------------------------------------- /data/templates/userbox.st: -------------------------------------------------------------------------------- 1 |
    2 | 6 |   7 | Login / Get an account 8 | Logout 9 |
    10 | -------------------------------------------------------------------------------- /data/templates/getuser.st: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /data/static/js/preview.js: -------------------------------------------------------------------------------- 1 | function updatePreviewPane() { 2 | $("#previewpane").hide(); 3 | var url = location.pathname.replace(/_edit\//,"_preview/"); 4 | $.post( 5 | url, 6 | {"raw" : $("#editedText").attr("value")}, 7 | function(data) { 8 | $('#previewpane').html(data); 9 | if (typeof(convert) == 'function') { convert(); } 10 | }, 11 | "html"); 12 | $('#previewpane').fadeIn(1000); 13 | }; 14 | $(document).ready(function(){ 15 | $("#previewButton").show(); 16 | }); 17 | 18 | -------------------------------------------------------------------------------- /data/FrontPage.page: -------------------------------------------------------------------------------- 1 | # Welcome to Gitit! 2 | 3 | This is the front page of your new gitit wiki. You can edit this 4 | page by clicking on the "edit" tab at the top of the screen. 5 | For instructions on how to make a link to another wiki page, see [the 6 | Help page](Help#wiki-links). To create a new wiki page, just create a 7 | link to it and follow the link. 8 | 9 | Help is always available through the "Help" link in the sidebar. 10 | More details on installing and configurating gitit are available 11 | in the [Gitit User's Guide](). 12 | 13 | -------------------------------------------------------------------------------- /plugins/Deprofanizer.hs: -------------------------------------------------------------------------------- 1 | module Deprofanizer (plugin) where 2 | 3 | -- This plugin replaces profane words with "XXXXX". 4 | 5 | import Network.Gitit.Interface 6 | import Data.Char (toLower) 7 | 8 | plugin :: Plugin 9 | plugin = mkPageTransform deprofanize 10 | 11 | deprofanize :: Inline -> Inline 12 | deprofanize (Str x) | isBadWord x = Str "XXXXX" 13 | deprofanize x = x 14 | 15 | isBadWord :: String -> Bool 16 | isBadWord x = map toLower x `elem` ["darn", "blasted", "stinker"] 17 | -- there are more, but this is a family program 18 | 19 | -------------------------------------------------------------------------------- /plugins/ShowUser.hs: -------------------------------------------------------------------------------- 1 | module ShowUser (plugin) where 2 | 3 | -- This plugin replaces $USER$ with the name of the currently logged in 4 | -- user, or the empty string if no one is logged in. 5 | 6 | import Network.Gitit.Interface 7 | 8 | plugin :: Plugin 9 | plugin = mkPageTransformM showuser 10 | 11 | showuser :: Inline -> PluginM Inline 12 | showuser (Math InlineMath x) | x == "USER" = do 13 | doNotCache -- tell gitit not to cache this page, as it has dynamic content 14 | mbUser <- askUser 15 | case mbUser of 16 | Nothing -> return $ Str "" 17 | Just u -> return $ Str $ uUsername u 18 | showuser x = return x 19 | 20 | -------------------------------------------------------------------------------- /plugins/CapitalizeEmphasis.hs: -------------------------------------------------------------------------------- 1 | module CapitalizeEmphasis (plugin) where 2 | 3 | -- This plugin converts emphasized text to ALL CAPS. 4 | -- Not a very useful feature, but useful as an example 5 | -- of how to write a plugin. 6 | 7 | import Network.Gitit.Interface 8 | import Data.Char (toUpper) 9 | 10 | plugin :: Plugin 11 | plugin = mkPageTransform capsTransform 12 | 13 | capsTransform :: [Inline] -> [Inline] 14 | capsTransform (Emph x : xs) = processWith capStr x ++ capsTransform xs 15 | capsTransform (x:xs) = x : capsTransform xs 16 | capsTransform [] = [] 17 | 18 | capStr :: Inline -> Inline 19 | capStr (Str x) = Str (map toUpper x) 20 | capStr x = x 21 | -------------------------------------------------------------------------------- /data/templates/pagetools.st: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | This page 4 | 12 | $exportbox$ 13 |
    14 |
    15 | -------------------------------------------------------------------------------- /plugins/Signature.hs: -------------------------------------------------------------------------------- 1 | module Signature (plugin) where 2 | 3 | -- This plugin replaces $SIG$ with the username and timestamp 4 | -- of the last edit, prior to saving the page in the repository. 5 | 6 | import Network.Gitit.Interface 7 | import Data.DateTime (getCurrentTime, formatDateTime) 8 | 9 | plugin :: Plugin 10 | plugin = PreCommitTransform replacedate 11 | 12 | replacedate :: String -> PluginM String 13 | replacedate [] = return "" 14 | replacedate ('$':'S':'I':'G':'$':xs) = do 15 | datetime <- liftIO getCurrentTime 16 | mbuser <- askUser 17 | let username = case mbuser of 18 | Nothing -> "???" 19 | Just u -> uUsername u 20 | let sig = concat ["-- ", username, " (", formatDateTime "%c" datetime, ")"] 21 | fmap (sig ++ ) $ replacedate xs 22 | replacedate (x:xs) = fmap (x : ) $ replacedate xs 23 | 24 | -------------------------------------------------------------------------------- /data/markupHelp/RST: -------------------------------------------------------------------------------- 1 | ~~~~~~~~ 2 | Section heading 3 | =============== 4 | 5 | Subsection 6 | ---------- 7 | 8 | Formatting: *italics*, 9 | **bold**. 10 | 11 | Indented quotation 12 | 13 | Links: 14 | `external `_, 15 | `Wiki Link <>`_, |image|, 16 | `heading <#subsection>`_. 17 | 18 | .. |image| image:: 19 | /img/gitit-banner.png 20 | 21 | :: 22 | 23 | let a = 1:a in head a 24 | 25 | - bulleted 26 | - list 27 | 28 | --------------- 29 | 30 | 1. ordered 31 | 2. list 32 | 33 | a. sublist (indent 4 spaces) 34 | b. another 35 | 36 | 3. item three 37 | 38 | term 39 | definition 40 | orange 41 | orange fruit 42 | 43 | ~~~~~~~~ 44 | 45 | For more: [reST primer], 46 | [quick reference guide]. 47 | 48 | [reST primer]: http://docutils.sourceforge.net/docs/user/rst/quickstart.html 49 | [quick reference guide]: http://docutils.sourceforge.net/docs/user/rst/quickref.html 50 | 51 | -------------------------------------------------------------------------------- /data/static/js/dragdiff.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | $("#content").prepend("

    Drag one revision onto another to see differences.

    "); 3 | $(".difflink").draggable({helper: "clone"}); 4 | $(".difflink").droppable({ 5 | accept: ".difflink", 6 | drop: function(ev, ui) { 7 | var targetOrder = parseInt($(this).attr("order")); 8 | var sourceOrder = parseInt($(ui.draggable).attr("order")); 9 | var diffurl = $(this).attr("diffurl"); 10 | if (targetOrder < sourceOrder) { 11 | var fromRev = $(this).attr("revision"); 12 | var toRev = $(ui.draggable).attr("revision"); 13 | } else { 14 | var toRev = $(this).attr("revision"); 15 | var fromRev = $(ui.draggable).attr("revision"); 16 | }; 17 | location.href = diffurl + '?from=' + fromRev + '&to=' + toRev; 18 | } 19 | }); 20 | }); 21 | 22 | -------------------------------------------------------------------------------- /data/markupHelp/Markdown: -------------------------------------------------------------------------------- 1 | ~~~~~~~~ 2 | # Section heading 3 | 4 | ## Subsection 5 | 6 | Formatting: *italics*, 7 | **bold**, super^script^, 8 | sub~script~, ~~strikeout~~. 9 | A line break 10 | can be forced with two spaces 11 | at the end of the line. 12 | 13 | > Indented quotation 14 | 15 | Links: 16 | [external](http://google.com), 17 | [Wiki Link](), 18 | ![image](/img/gitit-banner.png), 19 | [to heading](#section-heading). 20 | 21 | Indented code block: 22 | 23 | #include 24 | 25 | Or use a delimited code block: 26 | 27 | ~~~ { .haskell } 28 | let a = 1:a in head a 29 | ~~~ 30 | 31 | - bulleted 32 | - list 33 | 34 | * * * * * 35 | 36 | 1. ordered 37 | 2. list 38 | a. sublist (indent 4 spaces) 39 | b. another 40 | 3. item three 41 | 42 | term 43 | : definition 44 | orange 45 | : orange fruit 46 | 47 | ~~~~~~~~ 48 | 49 | For more: [markdown syntax](http://daringfireball.net/projects/markdown), 50 | [pandoc extensions](http://johnmacfarlane.net/pandoc/README.html). 51 | 52 | -------------------------------------------------------------------------------- /data/markupHelp/RST+LHS: -------------------------------------------------------------------------------- 1 | ~~~~~~~~ 2 | Section heading 3 | =============== 4 | 5 | Subsection 6 | ---------- 7 | 8 | Formatting: *italics*, 9 | **bold**. 10 | 11 | Indented quotation 12 | 13 | 14 | Links: 15 | `external `_, 16 | `Wiki Link <>`_, |image|, 17 | `heading <#subsection>`_. 18 | 19 | .. |image| image:: 20 | /img/gitit-banner.png 21 | 22 | :: 23 | 24 | let a = 1:a in head a 25 | 26 | > -- bird-style Haskell 27 | > fibs = 1 : 1 : zipWith (+) 28 | > fibs (tail fibs) 29 | 30 | - bulleted 31 | - list 32 | 33 | -------------- 34 | 35 | - ordered 36 | - list 37 | 38 | a. sublist (indent 4 spaces) 39 | b. another 40 | 41 | - item three 42 | 43 | term 44 | definition 45 | orange 46 | orange fruit 47 | 48 | ~~~~~~~~ 49 | 50 | For more: [reST primer], 51 | [quick reference guide]. 52 | 53 | [reST primer]: http://docutils.sourceforge.net/docs/user/rst/quickstart.html 54 | [quick reference guide]: http://docutils.sourceforge.net/docs/user/rst/quickref.html 55 | 56 | -------------------------------------------------------------------------------- /data/markupHelp/HTML: -------------------------------------------------------------------------------- 1 | ~~~~~~~~ 2 |

    Section heading

    3 |

    Subsection

    4 |

    Formatting: 5 | italics, 6 | bold, 7 | superscript, 8 | subscript, 9 | line
    break. 10 |

    11 |
    12 |

    Indented quotation

    13 |
    14 |

    Links: 15 | 16 | external, 17 | Wiki Link, 18 | image, 20 |

    21 |

    Indented code block:

    22 |
    
    23 | #include <stdbool.h>
    24 | 
    25 |
      26 |
    • bulleted
    • 27 |
    • list
    • 28 |
    29 |
    30 |
      31 |
    1. ordered
    2. 32 |
    3. list 33 |
        34 |
      1. sublist
      2. 35 |
      3. another
      4. 36 |
      37 |
    4. 38 |
    5. item three
    6. 39 |
    40 |
    41 |
    term
    42 |
    definition
    43 |
    orange
    44 |
    orange fruit
    45 |
    46 | ~~~~~~~~ 47 | 48 | For more: [xhtml tutorial](http://www.w3schools.com/Xhtml/), 49 | [pandoc](http://johnmacfarlane.net/pandoc/README.html). 50 | 51 | -------------------------------------------------------------------------------- /data/static/js/search.js: -------------------------------------------------------------------------------- 1 | jQuery.fn.highlightPattern = function (patt, className) 2 | { 3 | // patt is a space separated list of strings - we want to highlight 4 | // an occurrence of any of these strings as a separate word. 5 | var regex = new RegExp('\\b(' + patt.replace(/ /, '|') + ')\\b', 'g'); 6 | 7 | return this.each(function () 8 | { 9 | this.innerHTML = this.innerHTML.replace(regex, 10 | '' + '$1' + ''); 11 | }); 12 | }; 13 | function toggleMatches(obj) { 14 | var pattern = $('#pattern').text(); 15 | var matches = obj.next('.matches') 16 | matches.slideToggle(300); 17 | matches.highlightPattern(pattern, 'highlighted'); 18 | if (obj.html() == '[show matches]') { 19 | obj.html('[hide matches]'); 20 | } else { 21 | obj.html('[show matches]'); 22 | }; 23 | } 24 | $(function() { 25 | $('a.showmatch').attr('onClick', 'toggleMatches($(this));'); 26 | $('pre.matches').hide(); 27 | $('a.showmatch').show(); 28 | }); 29 | -------------------------------------------------------------------------------- /data/static/css/hk-pyg.css: -------------------------------------------------------------------------------- 1 | /* Loosely based on pygment's default colors */ 2 | table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode, table.sourceCode pre 3 | { margin: 0; padding: 0; border: 0; vertical-align: baseline; border: none; } 4 | td.lineNumbers { border-right: 1px solid #AAAAAA; text-align: right; color: #AAAAAA; padding-right: 5px; padding-left: 5px; } 5 | td.sourceCode { padding-left: 5px; } 6 | pre.sourceCode { } 7 | pre.sourceCode span.kw { color: #007020; font-weight: bold; } 8 | pre.sourceCode span.dt { color: #902000; } 9 | pre.sourceCode span.dv { color: #40a070; } 10 | pre.sourceCode span.bn { color: #40a070; } 11 | pre.sourceCode span.fl { color: #40a070; } 12 | pre.sourceCode span.ch { color: #4070a0; } 13 | pre.sourceCode span.st { color: #4070a0; } 14 | pre.sourceCode span.co { color: #60a0b0; font-style: italic; } 15 | pre.sourceCode span.ot { color: #007020; } 16 | pre.sourceCode span.al { color: red; font-weight: bold; } 17 | pre.sourceCode span.fu { color: #06287e; } 18 | pre.sourceCode span.re { } 19 | pre.sourceCode span.er { color: red; font-weight: bold; } 20 | -------------------------------------------------------------------------------- /plugins/PigLatin.hs: -------------------------------------------------------------------------------- 1 | module PigLatin (plugin) where 2 | 3 | -- This plugin converts a page to pig latin if the 'language' metadata 4 | -- field is set to 'pig latin'. This demonstrates how to get access to 5 | -- metadata in a plugin. 6 | 7 | import Network.Gitit.Interface 8 | import Data.Char (toLower, toUpper, isLower, isUpper, isLetter) 9 | 10 | plugin :: Plugin 11 | plugin = PageTransform $ \doc -> do 12 | meta <- askMeta 13 | case lookup "language" meta of 14 | Just s | map toLower s == "pig latin" -> 15 | return $ processWith pigLatinStr doc 16 | _ -> return doc 17 | 18 | pigLatinStr :: Inline -> Inline 19 | pigLatinStr (Str "") = Str "" 20 | pigLatinStr (Str (c:cs)) | isLower c && isConsonant c = 21 | Str (cs ++ (c : "ay")) 22 | pigLatinStr (Str (c:cs)) | isUpper c && isConsonant c = 23 | Str (capitalize cs ++ (toLower c : "ay")) 24 | pigLatinStr (Str x@(c:_)) | isLetter c = Str (x ++ "yay") 25 | pigLatinStr x = x 26 | 27 | isConsonant :: Char -> Bool 28 | isConsonant c = c `notElem` "aeiouAEIOU" 29 | 30 | capitalize :: String -> String 31 | capitalize "" = "" 32 | capitalize (c:cs) = toUpper c : cs 33 | -------------------------------------------------------------------------------- /data/markupHelp/LaTeX: -------------------------------------------------------------------------------- 1 | ~~~~~~~~ 2 | \section{Section heading} 3 | 4 | \subsection{Subsection} 5 | 6 | Formatting: \emph{italics}, 7 | \textbf{bold}, 8 | super\textsuperscript{script}, 9 | sub\textsubscr{script}, 10 | \sout{strikeout}. A line break\\ 11 | can be forced with \\ at 12 | the end of the line. 13 | 14 | \begin{quote} 15 | Indented quotation 16 | \end{quote} 17 | 18 | Links: 19 | \href{http://foo.bar}{external}, 20 | \href{}{Wiki Link}, 21 | \includegraphics{/img/banner.png}, 22 | \href{#subsection}{to heading}. 23 | 24 | \begin{verbatim} 25 | #include 26 | \end{verbatim} 27 | 28 | \begin{itemize} 29 | \item bulleted 30 | \item list 31 | \end{itemize} 32 | 33 | \begin{enumerate} 34 | \item ordered 35 | \item list 36 | \begin{enumerate}[a.] 37 | \item sublist 38 | \item another 39 | \end{enumerate} 40 | \item item three 41 | \end{enumerate} 42 | 43 | \begin{description} 44 | \item[term] definition 45 | \item[orange] orange fruit 46 | \end{description} 47 | 48 | ~~~~~~~~ 49 | 50 | For more: [LaTeX], [pandoc]. 51 | 52 | [LaTeX]: http://www.latex-project.org/ 53 | [pandoc]: http://johnmacfarlane.net/pandoc/README.html 54 | 55 | -------------------------------------------------------------------------------- /data/markup.HTML: -------------------------------------------------------------------------------- 1 | # Markup 2 | 3 | The syntax for wiki pages is standard XHTML. All tags must be 4 | properly closed. 5 | 6 | ## Wiki links 7 | 8 | Links to other wiki pages are formed this way: 9 | `Page Name`. (Gitit converts links with empty 10 | targets into wikilinks.) 11 | 12 | To link to a wiki page using something else as the link text: 13 | `something else`. 14 | 15 | Note that page names may contain spaces and some special 16 | characters. They need not be CamelCase. CamelCase words are *not* 17 | automatically converted to wiki links. 18 | 19 | Wiki pages may be organized into directories. So, if you have 20 | several pages on wine, you may wish to organize them like so: 21 | 22 | Wine/Pinot Noir 23 | Wine/Burgundy 24 | Wine/Cabernet Sauvignon 25 | 26 | Note that a wiki link `Burgundy` that occurs inside 27 | the `Wine` directory will link to `Wine/Burgundy`, and not to 28 | `Burgundy`. To link to a top-level page called `Burgundy`, you'd 29 | have to use `Burgundy`. 30 | 31 | To link to a directory listing for a subdirectory, use a trailing 32 | slash: `Wine/` will link to a listing of the `Wine` subdirectory. 33 | -------------------------------------------------------------------------------- /data/templates/sitenav.st: -------------------------------------------------------------------------------- 1 | 28 | -------------------------------------------------------------------------------- /data/markupHelp/Markdown+LHS: -------------------------------------------------------------------------------- 1 | ~~~~~~~~ 2 | # Section heading 3 | 4 | ## Subsection 5 | 6 | Formatting: *italics*, 7 | **bold**, super^script^, 8 | sub~script~, ~~strikeout~~. 9 | A line break 10 | can be forced with two spaces 11 | at the end of the line. 12 | 13 | > Indented quotation: 14 | > note: the '>' must not 15 | > be flush with the margin 16 | > or what follows will be 17 | > treated as Haskell code 18 | 19 | > -- bird-tracks Haskell: 20 | > fibs = 0 : 1 : 21 | > zipWith (+) fibs (tail fibs) 22 | 23 | Links: 24 | [external](http://google.com), 25 | [Wiki Link](), 26 | ![image](/img/gitit-banner.png), 27 | [to heading](#section-heading). 28 | 29 | Indented code block: 30 | 31 | #include 32 | 33 | Or use a delimited code block: 34 | 35 | ~~~ { .haskell } 36 | let a = 1:a in head a 37 | ~~~ 38 | 39 | - bulleted 40 | - list 41 | 42 | * * * * * 43 | 44 | 1. ordered 45 | 2. list 46 | a. sublist (indent 4 spaces) 47 | b. another 48 | 3. item three 49 | 50 | term 51 | : definition 52 | orange 53 | : orange fruit 54 | 55 | ~~~~~~~~ 56 | 57 | For more: [markdown syntax](http://daringfireball.net/projects/markdown), 58 | [pandoc extensions](http://johnmacfarlane.net/pandoc/README.html). 59 | 60 | -------------------------------------------------------------------------------- /data/markupHelp/LaTeX+LHS: -------------------------------------------------------------------------------- 1 | ~~~~~~~~ 2 | \section{Section heading} 3 | 4 | \subsection{Subsection} 5 | 6 | Formatting: \emph{italics}, 7 | \textbf{bold}, 8 | super\textsuperscript{script}, 9 | sub\textsubscr{script}, 10 | \sout{strikeout}. A line break\\ 11 | can be forced with \\ at 12 | the end of the line. 13 | 14 | \begin{quote} 15 | Indented quotation 16 | \end{quote} 17 | 18 | Links: 19 | \href{http://foo.bar}{external}, 20 | \href{}{Wiki Link}, 21 | \includegraphics{/img/banner.png}, 22 | \href{#subsection}{to heading}. 23 | 24 | \begin{verbatim} 25 | #include 26 | \end{verbatim} 27 | 28 | Haskell code: 29 | \begin{code} 30 | fibs = 1 : 1 : zipWith (+) 31 | fibs (tail fibs) 32 | \end{code} 33 | 34 | \begin{itemize} 35 | \item bulleted 36 | \item list 37 | \end{itemize} 38 | 39 | \begin{enumerate} 40 | \item ordered 41 | \item list 42 | \begin{enumerate}[a.] 43 | \item sublist 44 | \item another 45 | \end{enumerate} 46 | \item item three 47 | \end{enumerate} 48 | 49 | \begin{description} 50 | \item[term] definition 51 | \item[orange] orange fruit 52 | \end{description} 53 | 54 | ~~~~~~~~ 55 | 56 | For more: [LaTeX], [pandoc]. 57 | 58 | [LaTeX]: http://www.latex-project.org/ 59 | [pandoc]: http://johnmacfarlane.net/pandoc/README.html 60 | 61 | -------------------------------------------------------------------------------- /data/static/css/ie.css: -------------------------------------------------------------------------------- 1 | /* ie.css */ 2 | body {text-align:center;} 3 | .container {text-align:left;} 4 | * html .column {overflow-x:hidden;} 5 | * html legend {margin:-18px -8px 16px 0;padding:0;} 6 | ol {margin-left:2em;} 7 | sup {vertical-align:text-top;} 8 | sub {vertical-align:text-bottom;} 9 | html>body p code {*white-space:normal;} 10 | hr {margin:-8px auto 11px;} 11 | .container ul { list-style: disc outside; margin-left: 2em; } /* IE can't handle :before and :after */ 12 | .container ul li { text-indent: 0; margin-left: 0; } 13 | .container legend { margin-bottom: 1.6em; } /* IE form margin bug */ 14 | sup, sub { font-size: 100%; } /* IE superscript & subscript bug */ 15 | .container blockquote p, #content blockquote ul, #content blockquote ol, #content blockquote dl, #content blockquote pre, #content blockquote address, 16 | .container blockquote table, #content blockquote form, #content blockquote h1, #content blockquote h2, #content blockquote h3, #content blockquote h4, #content blockquote h5, #content blockquote h6 { margin-top: .8em; margin-bottom: .8em; } /* IE can't handle :first-child */ 17 | * html .container textarea, * html .container input { padding: 0; } /* IE < 7 form fix */ 18 | .container input[type='submit'], .container input[type='button'] { padding: 0; } /* IE 7 button fix */ 19 | .container legend+* { margin-top: 0; } /* we already added legend margin */ 20 | a abbr, a acronym { text-decoration: underline; } /* IE 7 bug */ 21 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | _ get wiki up to speed as main home page (demogitit). 2 | Add good Installing page. Up-to-date User's Guide. 3 | Front page should list features and link to good demos. 4 | 5 | ................................................................ 6 | 7 | _ consider making the library independent of datafiles, moving 8 | all of that to a separate gitit executable package? 9 | 10 | _ consider replacing Params with a bunch of newtypes, one for 11 | each sort of param. This would make the code more modular. 12 | 13 | _ document HTTP auth setup in README 14 | 15 | _ improve support for categories: 16 | _ categories in a tree structure 17 | _ editable category pages 18 | x improve look of category box at bottom of page? 19 | see greenrd's email, also: 20 | an example of a category: 21 | http://haskell.org/haskellwiki/Category:XMonad 22 | Note the subcategories as well. For general technical information 23 | and usage of MediaWiki categories, see 24 | https://secure.wikimedia.org/wikipedia/en/wiki/Help:Category & 25 | https://secure.wikimedia.org/wikipedia/en/wiki/Wikipedia:Categorization 26 | Categories can form a tree structure. 27 | Articles can be in multiple categories. 28 | 29 | _ paging for long histories, activity lists 30 | 31 | _ stabilize API 32 | 33 | _ cleanup code for 80 cols; hlint 34 | 35 | _ web page with simple instructions on installing gitit with 36 | cabal install and Haskell platform. 37 | 38 | _ release! 39 | 40 | -------------------------------------------------------------------------------- /plugins/Subst.hs: -------------------------------------------------------------------------------- 1 | -- Usage: a paragraph containing just [My page](!subst) 2 | -- will be replaced by the contents of My page. 3 | -- 4 | -- Limitations: it is assumed that My page is 5 | -- formatted with markdown, and contains no metadata. 6 | 7 | module Subst (plugin) where 8 | 9 | import Control.Monad.CatchIO (try) 10 | import Data.FileStore (FileStoreError, retrieve) 11 | import Text.Pandoc (defaultParserState, readMarkdown) 12 | import Network.Gitit.ContentTransformer (inlinesToString) 13 | import Network.Gitit.Interface 14 | import Network.Gitit.Framework (filestoreFromConfig) 15 | 16 | plugin :: Plugin 17 | plugin = mkPageTransformM substituteIntoBlock 18 | 19 | substituteIntoBlock :: [Block] -> PluginM [Block] 20 | substituteIntoBlock ((Para [Link ref ("!subst", _)]):xs) = 21 | do let target = inlinesToString ref 22 | cfg <- askConfig 23 | let fs = filestoreFromConfig cfg 24 | article <- try $ liftIO (retrieve fs (target ++ ".page") Nothing) 25 | case article :: Either FileStoreError String of 26 | Left _ -> let txt = Str ("[" ++ target ++ "](!subst)") 27 | alt = "'" ++ target ++ "' doesn't exist. Click here to create it." 28 | lnk = Para [Link [txt] (target,alt)] 29 | in (lnk :) `fmap` substituteIntoBlock xs 30 | Right a -> let (Pandoc _ content) = readMarkdown defaultParserState a 31 | in (content ++) `fmap` substituteIntoBlock xs 32 | substituteIntoBlock (x:xs) = (x:) `fmap` substituteIntoBlock xs 33 | substituteIntoBlock [] = return [] 34 | -------------------------------------------------------------------------------- /YUI-LICENSE: -------------------------------------------------------------------------------- 1 | Software License Agreement (BSD License) 2 | Copyright (c) 2009, Yahoo! Inc. 3 | All rights reserved. 4 | 5 | Redistribution and use of this software in source and binary forms, 6 | with or without modification, are permitted provided that the following 7 | conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the name of Yahoo! Inc. nor the names of its contributors 17 | may be used to endorse or promote products derived from this software 18 | without specific prior written permission of Yahoo! Inc. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 26 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /data/markup.RST: -------------------------------------------------------------------------------- 1 | # Markup 2 | 3 | This wiki's pages are written in [reStructuredText]. If you're 4 | not familiar with reStructuredText, you should start by looking at 5 | the [primer] and the [quick reference guide]. Note that not all 6 | reStructuredText constructs are currently supported. Use the 7 | preview button if you're in doubt. 8 | 9 | [reStructuredText]: http://docutils.sourceforge.net/rst.html 10 | [primer]: http://docutils.sourceforge.net/docs/user/rst/quickstart.html 11 | [quick reference guide]: http://docutils.sourceforge.net/docs/user/rst/quickref.html 12 | 13 | ## Wiki links 14 | 15 | Links to other wiki pages are formed this way: `` `Page Name <>`_ ``. 16 | (Gitit converts markdown links with empty targets into wikilinks.) 17 | 18 | To link to a wiki page using something else as the link text: 19 | either `` `something else `_ `` or 20 | 21 | `something else`_ 22 | 23 | .. _`something else`: Page Name 24 | 25 | Note that page names may contain spaces and some special 26 | characters. They need not be CamelCase. CamelCase words are *not* 27 | automatically converted to wiki links. 28 | 29 | Wiki pages may be organized into directories. So, if you have 30 | several pages on wine, you may wish to organize them like so: 31 | 32 | Wine/Pinot Noir 33 | Wine/Burgundy 34 | Wine/Cabernet Sauvignon 35 | 36 | Note that a wiki link `` `Burgundy <>`_ `` that occurs inside the `Wine` 37 | directory will link to `Wine/Burgundy`, and not to `Burgundy`. To 38 | link to a top-level page called `Burgundy`, you'd have to use 39 | `` `Burgundy `_ ``. 40 | 41 | To link to a directory listing for a subdirectory, use a trailing 42 | slash: `` `Wine/ <>`_ `` will link to a listing of the `Wine` subdirectory. 43 | -------------------------------------------------------------------------------- /data/templates/page.st: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | $if(feed)$ 7 | 8 | 9 | $endif$ 10 | $wikititle$ - $pagetitle$ 11 | $if(printable)$ 12 | 13 | $else$ 14 | 15 | 16 | $endif$ 17 | 18 | 19 | 20 |
    21 |
    22 |
    23 | $userbox()$ 24 | $tabs$ 25 | $content()$ 26 | $footer()$ 27 |
    28 |
    29 | 41 |
    42 | $javascripts$ 43 | $expire()$ 44 | $getuser()$ 45 | 46 | 47 | -------------------------------------------------------------------------------- /plugins/Dot.hs: -------------------------------------------------------------------------------- 1 | module Dot (plugin) where 2 | 3 | -- This plugin allows you to include a graphviz dot diagram 4 | -- in a page like this: 5 | -- 6 | -- ~~~ {.dot name="diagram1"} 7 | -- digraph G {Hello->World} 8 | -- ~~~ 9 | -- 10 | -- The "dot" executable must be in the path. 11 | -- The generated png file will be saved in the static img directory. 12 | -- If no name is specified, a unique name will be generated from a hash 13 | -- of the file contents. 14 | 15 | import Network.Gitit.Interface 16 | import System.Process (readProcessWithExitCode) 17 | import System.Exit (ExitCode(ExitSuccess)) 18 | -- from the utf8-string package on HackageDB: 19 | import Data.ByteString.Lazy.UTF8 (fromString) 20 | -- from the SHA package on HackageDB: 21 | import Data.Digest.Pure.SHA (sha1, showDigest) 22 | import System.FilePath (()) 23 | import Control.Monad.Trans (liftIO) 24 | 25 | plugin :: Plugin 26 | plugin = mkPageTransformM transformBlock 27 | 28 | transformBlock :: Block -> PluginM Block 29 | transformBlock (CodeBlock (_, classes, namevals) contents) | "dot" `elem` classes = do 30 | cfg <- askConfig 31 | let (name, outfile) = case lookup "name" namevals of 32 | Just fn -> ([Str fn], fn ++ ".png") 33 | Nothing -> ([], uniqueName contents ++ ".png") 34 | liftIO $ do 35 | (ec, _out, err) <- readProcessWithExitCode "dot" ["-Tpng", "-o", 36 | staticDir cfg "img" outfile] contents 37 | if ec == ExitSuccess 38 | then return $ Para [Image name ("/img" outfile, "")] 39 | else error $ "dot returned an error status: " ++ err 40 | transformBlock x = return x 41 | 42 | -- | Generate a unique filename given the file's contents. 43 | uniqueName :: String -> String 44 | uniqueName = showDigest . sha1 . fromString 45 | 46 | -------------------------------------------------------------------------------- /data/markup.LaTeX: -------------------------------------------------------------------------------- 1 | # Markup 2 | 3 | This wiki's pages are written in (a subset of) [LaTeX]. 4 | 5 | [LaTeX]: http://www.latex-project.org/ 6 | 7 | The following commands and environments are recognized: 8 | 9 | - `\emph{emphasis}` 10 | 11 | - `\textbf{bold}` 12 | 13 | - `verb!verbatim@@\#!` 14 | 15 | - `\textsubscr{2}` 16 | 17 | - `\sout{strikeout}` 18 | 19 | - `\textsuperscript{2}` 20 | 21 | - `$e = mc^2$` 22 | 23 | - `$$e = mc^2$$` 24 | 25 | - `\footnote{a footnote}` 26 | 27 | - `\section{section}`, `\subsection{subsection}`, etc. 28 | 29 | - `\begin{quote} . . . \end{quote}` 30 | 31 | - `\begin{verbatim} . . . \end{verbatim}` 32 | 33 | - `\begin{itemize} . . . \end{itemize}` 34 | 35 | - `\begin{enumerate} . . . \end{enumerate}` 36 | 37 | ## Wiki links 38 | 39 | Links to other wiki pages are formed this way: `\href{}{Page Name}`. 40 | (Gitit converts markdown links with empty targets into wikilinks.) 41 | 42 | To link to a wiki page using something else as the link text: 43 | `\href{Page Name}{Something else}`. 44 | 45 | Note that page names may contain spaces and some special 46 | characters. They need not be CamelCase. CamelCase words are *not* 47 | automatically converted to wiki links. 48 | 49 | Wiki pages may be organized into directories. So, if you have 50 | several pages on wine, you may wish to organize them like so: 51 | 52 | Wine/Pinot Noir 53 | Wine/Burgundy 54 | Wine/Cabernet Sauvignon 55 | 56 | Note that a wiki link `\href{}{Burgundy}` that occurs inside the `Wine` 57 | directory will link to `Wine/Burgundy`, and not to `Burgundy`. To 58 | link to a top-level page called `Burgundy`, you'd have to use 59 | `\href{/Burgundy}{Burgundy}`. 60 | 61 | To link to a directory listing for a subdirectory, use a trailing 62 | slash: `\href{}{Wine/}` will link to a listing of the `Wine` subdirectory. 63 | 64 | -------------------------------------------------------------------------------- /data/static/css/print.css: -------------------------------------------------------------------------------- 1 | body { 2 | width:100% !important; 3 | margin:0 !important; 4 | padding:0 !important; 5 | line-height: 1.4; 6 | word-spacing:1.1pt; 7 | letter-spacing:0.2pt; font-family: "Times New Roman", serif; color: #000; background: none; font-size: 12pt; } 8 | 9 | /*Headings */ 10 | h1,h2,h3,h4,h5,h6 { font-family: Helvetica, Arial, sans-serif; } 11 | h1{font-size:19pt;} 12 | h2{font-size:17pt;} 13 | h3{font-size:15pt;} 14 | h4,h5,h6{font-size:12pt;} 15 | 16 | h2.revision { font-size: 10pt; font-weight: normal; font-style: italic; text-align: right; } 17 | 18 | pre, code { font: 10pt Courier, monospace; } 19 | blockquote { margin: 1.3em; padding: 1em; font-size: 10pt; } 20 | hr { background-color: #ccc; } 21 | 22 | /* Images */ 23 | img { float: left; margin: 1em 1.5em 1.5em 0; } 24 | a img { border: none; } 25 | 26 | /* Links */ 27 | a:link, a:visited { background: transparent; font-weight: normal; text-decoration: underline; color:#333; } 28 | a:link[href^="http://"]:after, a[href^="http://"]:visited:after { content: " (" attr(href) ")"; font-size: 90%; } 29 | a[href^="http://"] {color:#000; } 30 | 31 | /* Table */ 32 | table { margin: 1px; text-align:left; } 33 | th { font-weight: bold; } 34 | th,td { padding: 4px 10px 4px 0; } 35 | tfoot { font-style: italic; } 36 | caption { background: #fff; margin-bottom:2em; text-align:left; } 37 | thead {display: table-header-group;} 38 | tr {page-break-inside: avoid;} 39 | 40 | /*hide various parts from the site*/ 41 | 42 | #maincol { margin-left: 1em; margin-right: 1em; border: none; } 43 | #content { border: none; } 44 | #sidebar, #userbox, #footer {display:none;} 45 | #toc { display: none; } 46 | #categoryList { display: none; } 47 | h1 a:link, h2 a:link, h3 a:link, h4 a:link, h5 a:link, h6 a:link { text-decoration: none; } 48 | h1.pageTitle { font-size: 220%; } 49 | td.lineNumbers { display: none; } 50 | ul.tabs { display: none; } 51 | -------------------------------------------------------------------------------- /HCAR-gitit.tex: -------------------------------------------------------------------------------- 1 | % gitit-Jg.tex 2 | \begin{hcarentry}[updated]{gitit} 3 | \label{gitit} 4 | \report{John MacFarlane}%11/10 5 | \participants{Gwern Branwen, Simon Michael, Henry Laxen, Anton 6 | van Straaten, Robin Green, Thomas Hartman, Justin Bogner, Kohei Ozaki, 7 | Dmitry Golubovsky, Anton Tayanovskyy, Dan Cook, Jinjing Wang} 8 | \status{active development} 9 | \makeheader 10 | 11 | Gitit is a wiki built on Happstack~\cref{happstack} and backed by a git, darcs, or mercurial 12 | filestore. Pages and uploaded files can be modified either directly 13 | via the VCS's command-line tools or through the wiki's web interface. 14 | Pandoc~\cref{pandoc} is used for markup processing, so pages may be written in 15 | (extended) markdown, reStructuredText, LaTeX, HTML, or literate Haskell, 16 | and exported in thirteen different formats, including LaTeX, ConTeXt, 17 | DocBook, RTF, OpenOffice ODT, MediaWiki markup, EPUB, and PDF. 18 | 19 | Notable features of gitit include: 20 | \begin{compactitem} 21 | \item 22 | Plugins: users can write their own dynamically loaded page transformations, 23 | which operate directly on the abstract syntax tree. 24 | \item 25 | Math support: LaTeX inline and display math is automatically converted 26 | to MathML, using the \texttt{texmath} library. 27 | \item 28 | Highlighting: Any git, darcs, or mercurial repository can be made a gitit wiki. 29 | Directories can be browsed, and source code files are 30 | automatically syntax-highlighted. Code snippets in wiki pages 31 | can also be highlighted. 32 | \item 33 | Library: Gitit now exports a library, \texttt{Network.Gitit}, that makes it 34 | easy to include a gitit wiki (or wikis) in any Happstack application. 35 | \item 36 | Literate Haskell: Pages can be written directly in literate Haskell. 37 | \end{compactitem} 38 | 39 | \FurtherReading 40 | \url{http://gitit.net} (itself 41 | a running demo of gitit) 42 | \end{hcarentry} 43 | -------------------------------------------------------------------------------- /expireGititCache.hs: -------------------------------------------------------------------------------- 1 | {- 2 | expireGititCache - (C) 2009 John MacFarlane, licensed under the GPL 3 | 4 | This program is designed to be used in post-update hooks and other scripts. 5 | 6 | Usage: expireGititCache base-url [file..] 7 | 8 | Example: 9 | 10 | expireGititCache http://localhost:5001 page1.page foo/bar.hs "Front Page.page" 11 | 12 | will produce POST requests to http://localhost:5001/_expire/page1, 13 | http://localhost:5001/_expire/foo/bar.hs, and 14 | http://localhost:5001/_expire/Front Page. 15 | 16 | Return statuses: 17 | 18 | 0 -> the cached page was successfully expired (or was not cached in the first place) 19 | 1 -> fewer than two arguments were supplied 20 | 3 -> did not receive a 200 OK response from the request 21 | 5 -> could not parse the uri 22 | 23 | -} 24 | 25 | module Main 26 | where 27 | import Network.HTTP 28 | import System.Environment 29 | import Network.URI 30 | import System.FilePath 31 | import Control.Monad 32 | import System.IO 33 | import System.Exit 34 | 35 | main :: IO () 36 | main = do 37 | args <- getArgs 38 | (uriString : files) <- if length args < 2 39 | then usageMessage >> return [""] 40 | else return args 41 | uri <- case parseURI uriString of 42 | Just u -> return u 43 | Nothing -> do 44 | hPutStrLn stderr ("Could not parse URI " ++ uriString) 45 | exitWith (ExitFailure 5) 46 | forM_ files (expireFile uri) 47 | 48 | usageMessage :: IO () 49 | usageMessage = do 50 | hPutStrLn stderr $ "Usage: expireGititCache base-url [file..]\n" ++ 51 | "Example: expireGititCache http://localhost:5001 page1.page foo/bar.hs" 52 | exitWith (ExitFailure 1) 53 | 54 | expireFile :: URI -> FilePath -> IO () 55 | expireFile uri file = do 56 | let path' = if takeExtension file == ".page" 57 | then dropExtension file 58 | else file 59 | let uri' = uri{uriPath = "/_expire/" ++ urlEncode path'} 60 | resResp <- simpleHTTP Request{rqURI = uri', rqMethod = POST, rqHeaders = [], rqBody = ""} 61 | case resResp of 62 | Left connErr -> error $ show connErr 63 | Right (Response (2,0,0) _ _ _) -> return () 64 | _ -> do 65 | hPutStrLn stderr ("Request for " ++ show uri' ++ " did not return success status") 66 | exitWith (ExitFailure 3) 67 | -------------------------------------------------------------------------------- /plugins/ImgTex.hs: -------------------------------------------------------------------------------- 1 | module ImgTex (plugin) where 2 | {- 3 | 4 | This plugin provides a clear math LaTeX output. 5 | (* latex and dvipng executable must be in the path.) 6 | 7 | like this: 8 | 9 | ~~~ {.dvipng} 10 | \nabla \times \bm{V} 11 | = 12 | \frac{1}{h_1 h_2 h_3} 13 | \begin{vmatrix} 14 | h_1 e_1 & h_2 e_2 & h_3 e_3 \\ 15 | \frac{\partial}{\partial q_{1}} & 16 | \frac{\partial}{\partial q_{2}} & 17 | \frac{\partial}{\partial q_{3}} \\ 18 | h_1 V_1 & h_2 V_2 & h_3 V_3 19 | \end{vmatrix} 20 | ~~~ 21 | 22 | License: GPL 23 | written by Kohei OZAKI 24 | modified by John MacFarlane to use withTempDir 25 | 26 | -} 27 | 28 | import Network.Gitit.Interface 29 | import System.Process (system) 30 | import System.Directory 31 | import Data.ByteString.Lazy.UTF8 (fromString) 32 | import Data.Digest.Pure.SHA 33 | import System.FilePath 34 | import Control.Monad.Trans (liftIO) 35 | 36 | plugin :: Plugin 37 | plugin = mkPageTransformM transformBlock 38 | 39 | templateHeader, templateFooter :: String 40 | templateHeader = concat 41 | [ "\\documentclass[12pt]{article}\n" 42 | , "\\usepackage{amsmath,amssymb,bm}\n" 43 | , "\\begin{document}\n" 44 | , "\\thispagestyle{empty}\n" 45 | , "\\[\n"] 46 | 47 | templateFooter = 48 | "\n" 49 | ++ "\\]\n" 50 | ++ "\\end{document}\n" 51 | 52 | transformBlock :: Block -> PluginM Block 53 | transformBlock (CodeBlock (_, classes, namevals) contents) 54 | | "dvipng" `elem` classes = do 55 | cfg <- askConfig 56 | let (name, outfile) = case lookup "name" namevals of 57 | Just fn -> ([Str fn], fn ++ ".png") 58 | Nothing -> ([], uniqueName contents ++ ".png") 59 | curr <- liftIO getCurrentDirectory 60 | liftIO $ withTempDir "gitit-imgtex" $ \tmpdir -> do 61 | setCurrentDirectory tmpdir 62 | writeFile (outfile ++ ".tex") (templateHeader ++ contents ++ templateFooter) 63 | system $ "latex " ++ outfile ++ ".tex > /dev/null" 64 | setCurrentDirectory curr 65 | system $ "dvipng -T tight -bd 1000 -freetype0 -Q 5 --gamma 1.3 " ++ 66 | (tmpdir outfile <.> "dvi") ++ " -o " ++ (staticDir cfg "img" outfile) 67 | return $ Para [Image name ("/img" outfile, "")] 68 | transformBlock x = return x 69 | 70 | uniqueName :: String -> String 71 | uniqueName = showDigest . sha1 . fromString 72 | -------------------------------------------------------------------------------- /data/post-update: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This hook does two things: 4 | # 5 | # 1. update the "info" files that allow the list of references to be 6 | # queries over dumb transports such as http 7 | # 8 | # 2. if this repository looks like it is a non-bare repository, and 9 | # the checked-out branch is pushed to, then update the working copy. 10 | # This makes "push" function somewhat similarly to darcs and bzr. 11 | # 12 | # To enable this hook, make this file executable by "chmod +x post-update". 13 | 14 | git-update-server-info 15 | 16 | is_bare=$(git-config --get --bool core.bare) 17 | 18 | if [ -z "$is_bare" ] 19 | then 20 | # for compatibility's sake, guess 21 | git_dir_full=$(cd $GIT_DIR; pwd) 22 | case $git_dir_full in */.git) is_bare=false;; *) is_bare=true;; esac 23 | fi 24 | 25 | update_wc() { 26 | ref=$1 27 | echo "Push to checked out branch $ref" >&2 28 | if [ ! -f $GIT_DIR/logs/HEAD ] 29 | then 30 | echo "E:push to non-bare repository requires a HEAD reflog" >&2 31 | exit 1 32 | fi 33 | if (cd $GIT_WORK_TREE; git-diff-files -q --exit-code >/dev/null) 34 | then 35 | wc_dirty=0 36 | else 37 | echo "W:unstaged changes found in working copy" >&2 38 | wc_dirty=1 39 | desc="working copy" 40 | fi 41 | if git diff-index --cached HEAD@{1} >/dev/null 42 | then 43 | index_dirty=0 44 | else 45 | echo "W:uncommitted, staged changes found" >&2 46 | index_dirty=1 47 | if [ -n "$desc" ] 48 | then 49 | desc="$desc and index" 50 | else 51 | desc="index" 52 | fi 53 | fi 54 | if [ "$wc_dirty" -ne 0 -o "$index_dirty" -ne 0 ] 55 | then 56 | new=$(git rev-parse HEAD) 57 | echo "W:stashing dirty $desc - see git-stash(1)" >&2 58 | ( trap 'echo trapped $$; git symbolic-ref HEAD "'"$ref"'"' 2 3 13 15 ERR EXIT 59 | git-update-ref --no-deref HEAD HEAD@{1} 60 | cd $GIT_WORK_TREE 61 | git stash save "dirty $desc before update to $new"; 62 | git-symbolic-ref HEAD "$ref" 63 | ) 64 | fi 65 | 66 | # eye candy - show the WC updates :) 67 | echo "Updating working copy" >&2 68 | (cd $GIT_WORK_TREE 69 | git-diff-index -R --name-status HEAD >&2 70 | git-reset --hard HEAD) 71 | } 72 | 73 | if [ "$is_bare" = "false" ] 74 | then 75 | active_branch=`git-symbolic-ref HEAD` 76 | export GIT_DIR=$(cd $GIT_DIR; pwd) 77 | GIT_WORK_TREE=${GIT_WORK_TREE-..} 78 | for ref 79 | do 80 | if [ "$ref" = "$active_branch" ] 81 | then 82 | update_wc $ref 83 | fi 84 | done 85 | fi 86 | -------------------------------------------------------------------------------- /Network/Gitit/Server.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# OPTIONS_GHC -fno-warn-orphans #-} 3 | {- 4 | Copyright (C) 2008 John MacFarlane 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program; if not, write to the Free Software 18 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | -} 20 | 21 | {- Re-exports Happstack functions needed by gitit, including 22 | replacements for Happstack functions that don't handle UTF-8 properly, and 23 | new functions for setting headers and zipping contents and for looking up IP 24 | addresses. 25 | -} 26 | 27 | module Network.Gitit.Server 28 | ( module Happstack.Server 29 | , withExpiresHeaders 30 | , setContentType 31 | , setFilename 32 | , lookupIPAddr 33 | , getHost 34 | , compressedResponseFilter 35 | ) 36 | where 37 | import Happstack.Server 38 | import Happstack.Server.Parts (compressedResponseFilter) 39 | import Network.Socket (getAddrInfo, defaultHints, addrAddress) 40 | import Control.Monad.Reader 41 | import Data.ByteString.UTF8 as U 42 | 43 | withExpiresHeaders :: ServerMonad m => m Response -> m Response 44 | withExpiresHeaders = liftM (setHeader "Cache-Control" "max-age=21600") 45 | 46 | setContentType :: String -> Response -> Response 47 | setContentType = setHeader "Content-Type" 48 | 49 | setFilename :: String -> Response -> Response 50 | setFilename = setHeader "Content-Disposition" . \fname -> "attachment; filename=\"" ++ fname ++ "\"" 51 | 52 | -- IP lookup 53 | 54 | lookupIPAddr :: String -> IO (Maybe String) 55 | lookupIPAddr hostname = do 56 | addrs <- getAddrInfo (Just defaultHints) (Just hostname) Nothing 57 | if null addrs 58 | then return Nothing 59 | else return $ Just $ takeWhile (/=':') $ show $ addrAddress $ case addrs of -- head addrs 60 | [] -> error $ "lookupIPAddr, no addrs" 61 | (x:_) -> x 62 | getHost :: ServerMonad m => m (Maybe String) 63 | getHost = liftM (maybe Nothing (Just . U.toString)) $ getHeaderM "Host" 64 | -------------------------------------------------------------------------------- /plugins/WebArchiver.hs: -------------------------------------------------------------------------------- 1 | {-| Scans page of Markdown looking for http links. When it finds them, it submits them 2 | to webcitation.org / https://secure.wikimedia.org/wikipedia/en/wiki/WebCite 3 | (It will also submit them to Alexa (the source for the Internet Archive), but Alexa says that 4 | its bots take weeks to visit and may not ever.) 5 | 6 | Limitations: 7 | * Only parses Markdown, not ReST or any other format; this is because 'readMarkdown' 8 | is hardwired into it. 9 | 10 | By: Gwern Branwen; placed in the public domain -} 11 | 12 | module WebArchiver (plugin) where 13 | 14 | import Control.Concurrent (forkIO) 15 | import Control.Monad (when) 16 | import Control.Monad.Trans (MonadIO) 17 | import Data.Maybe (fromJust) 18 | import Network.Browser (browse, formToRequest, request, Form(..)) 19 | import Network.HTTP (getRequest, rspBody, simpleHTTP, RequestMethod(POST)) 20 | import Network.URI (isURI, parseURI, uriPath) 21 | 22 | import Network.Gitit.Interface (askUser, liftIO, processWithM, uEmail, Plugin(PreCommitTransform), Inline(Link)) 23 | import Text.Pandoc (defaultParserState, readMarkdown) 24 | 25 | plugin :: Plugin 26 | plugin = PreCommitTransform archivePage 27 | 28 | -- archivePage :: (MonadIO m) => String -> ReaderT (Config, Maybe User) (StateT IO) String 29 | archivePage x = do mbUser <- askUser 30 | let email = case mbUser of 31 | Nothing -> "nobody@mailinator.com" 32 | Just u -> uEmail u 33 | let p = readMarkdown defaultParserState x 34 | -- force evaluation and archiving side-effects 35 | _p' <- liftIO $ processWithM (archiveLinks email) p 36 | return x -- note: this is read-only - don't actually change page! 37 | 38 | archiveLinks :: String -> Inline -> IO Inline 39 | archiveLinks e x@(Link _ (uln, _)) = forkIO (checkArchive e uln) >> return x 40 | archiveLinks _ x = return x 41 | 42 | -- | Error check the URL and then archive it both ways 43 | checkArchive :: (MonadIO m) => String -> String -> m () 44 | checkArchive email url = when (isURI url) $ liftIO (webciteArchive email url >> alexaArchive url) 45 | 46 | webciteArchive :: String -> String -> IO () 47 | webciteArchive email url = ignore $ openURL ("http://www.webcitation.org/archive?url=" ++ url ++ "&email=" ++ email) 48 | where openURL = simpleHTTP . getRequest 49 | ignore = fmap $ const () 50 | 51 | alexaArchive :: String -> IO () 52 | alexaArchive url = do let archiveform = Form POST 53 | (fromJust $ parseURI "http://www.alexa.com/help/crawlrequest") 54 | [("url", url), ("submit", "")] 55 | (uri, resp) <- browse $ request $ formToRequest archiveform 56 | when (uriPath uri /= "/help/crawlthanks") $ 57 | error $ "Request failed! Did Alexa change webpages? Response:" ++ rspBody resp 58 | -------------------------------------------------------------------------------- /Network/Gitit/Cache.hs: -------------------------------------------------------------------------------- 1 | {- 2 | Copyright (C) 2008 John MacFarlane 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program; if not, write to the Free Software 16 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | -} 18 | 19 | {- Functions for maintaining user list and session state. 20 | -} 21 | 22 | module Network.Gitit.Cache ( expireCachedFile 23 | , lookupCache 24 | , cacheContents ) 25 | where 26 | 27 | import qualified Data.ByteString as B (ByteString, readFile, writeFile) 28 | import System.FilePath 29 | import System.Directory (doesFileExist, removeFile, createDirectoryIfMissing, getModificationTime) 30 | import System.Time (ClockTime) 31 | import Network.Gitit.State 32 | import Network.Gitit.Types 33 | import Control.Monad 34 | import Control.Monad.Trans (liftIO) 35 | import Codec.Binary.UTF8.String (encodeString) 36 | 37 | -- | Expire a cached file, identified by its filename in the filestore. 38 | -- If there is an associated exported PDF, expire it too. 39 | -- Returns () after deleting a file from the cache, fails if no cached file. 40 | expireCachedFile :: String -> GititServerPart () 41 | expireCachedFile file = do 42 | cfg <- getConfig 43 | let target = encodeString $ cacheDir cfg file 44 | exists <- liftIO $ doesFileExist target 45 | when exists $ liftIO $ do 46 | liftIO $ removeFile target 47 | expireCachedPDF target 48 | 49 | expireCachedPDF :: String -> IO () 50 | expireCachedPDF file = 51 | when (takeExtension file == ".page") $ do 52 | let pdfname = file ++ ".export.pdf" 53 | exists <- doesFileExist pdfname 54 | when exists $ removeFile pdfname 55 | 56 | lookupCache :: String -> GititServerPart (Maybe (ClockTime, B.ByteString)) 57 | lookupCache file = do 58 | cfg <- getConfig 59 | let target = encodeString $ cacheDir cfg file 60 | exists <- liftIO $ doesFileExist target 61 | if exists 62 | then liftIO $ do 63 | modtime <- getModificationTime target 64 | contents <- B.readFile target 65 | return $ Just (modtime, contents) 66 | else return Nothing 67 | 68 | cacheContents :: String -> B.ByteString -> GititServerPart () 69 | cacheContents file contents = do 70 | cfg <- getConfig 71 | let target = encodeString $ cacheDir cfg file 72 | let targetDir = takeDirectory target 73 | liftIO $ do 74 | createDirectoryIfMissing True targetDir 75 | B.writeFile target contents 76 | expireCachedPDF target 77 | -------------------------------------------------------------------------------- /Network/Gitit/Plugins.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {- 3 | Copyright (C) 2009 John MacFarlane 4 | 5 | This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program; if not, write to the Free Software 17 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | -} 19 | 20 | {- Functions for loading plugins. 21 | -} 22 | 23 | module Network.Gitit.Plugins ( loadPlugin, loadPlugins ) 24 | where 25 | import Network.Gitit.Types 26 | import System.FilePath 27 | import Control.Monad (unless) 28 | import System.Log.Logger (logM, Priority(..)) 29 | #ifdef _PLUGINS 30 | import Data.List (isInfixOf, isPrefixOf) 31 | import GHC 32 | import GHC.Paths 33 | import Unsafe.Coerce 34 | 35 | loadPlugin :: FilePath -> IO Plugin 36 | loadPlugin pluginName = do 37 | logM "gitit" WARNING ("Loading plugin '" ++ pluginName ++ "'...") 38 | runGhc (Just libdir) $ do 39 | dflags <- getSessionDynFlags 40 | setSessionDynFlags dflags 41 | defaultCleanupHandler dflags $ do 42 | -- initDynFlags 43 | unless ("Network.Gitit.Plugin." `isPrefixOf` pluginName) 44 | $ do 45 | addTarget =<< guessTarget pluginName Nothing 46 | r <- load LoadAllTargets 47 | case r of 48 | Failed -> error $ "Error loading plugin: " ++ pluginName 49 | Succeeded -> return () 50 | let modName = 51 | if "Network.Gitit.Plugin" `isPrefixOf` pluginName 52 | then pluginName 53 | else if "Network/Gitit/Plugin/" `isInfixOf` pluginName 54 | then "Network.Gitit.Plugin." ++ takeBaseName pluginName 55 | else takeBaseName pluginName 56 | pr <- findModule (mkModuleName "Prelude") Nothing 57 | i <- findModule (mkModuleName "Network.Gitit.Interface") Nothing 58 | m <- findModule (mkModuleName modName) Nothing 59 | setContext [] 60 | #if MIN_VERSION_ghc(7,0,0) 61 | [(m, Nothing), (i, Nothing), (pr, Nothing)] 62 | #else 63 | [m, i, pr] 64 | #endif 65 | value <- compileExpr (modName ++ ".plugin :: Plugin") 66 | let value' = (unsafeCoerce value) :: Plugin 67 | return value' 68 | 69 | #else 70 | 71 | loadPlugin :: FilePath -> IO Plugin 72 | loadPlugin pluginName = do 73 | error $ "Cannot load plugin '" ++ pluginName ++ 74 | "'. gitit was not compiled with plugin support." 75 | return undefined 76 | 77 | #endif 78 | 79 | loadPlugins :: [FilePath] -> IO [Plugin] 80 | loadPlugins pluginNames = do 81 | plugins' <- mapM loadPlugin pluginNames 82 | unless (null pluginNames) $ logM "gitit" WARNING "Finished loading plugins." 83 | return plugins' 84 | 85 | -------------------------------------------------------------------------------- /data/static/js/MathMLinHTML.js: -------------------------------------------------------------------------------- 1 | /* 2 | March 19, 2004 MathHTML (c) Peter Jipsen http://www.chapman.edu/~jipsen 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or (at 7 | your option) any later version. 8 | This program is distributed in the hope that it will be useful, but 9 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 10 | or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 11 | (at http://www.gnu.org/copyleft/gpl.html) for more details. 12 | */ 13 | 14 | function convertMath(node) {// for Gecko 15 | if (node.nodeType==1) { 16 | var newnode = 17 | document.createElementNS("http://www.w3.org/1998/Math/MathML", 18 | node.nodeName.toLowerCase()); 19 | for(var i=0; i < node.attributes.length; i++) 20 | newnode.setAttribute(node.attributes[i].nodeName, 21 | node.attributes[i].nodeValue); 22 | for (var i=0; i"); 69 | document.write(""); 70 | } 71 | if(typeof window.addEventListener != 'undefined'){ 72 | window.addEventListener('load', convert, false); 73 | } 74 | if(typeof window.attachEvent != 'undefined') { 75 | window.attachEvent('onload', convert); 76 | } 77 | -------------------------------------------------------------------------------- /Network/Gitit/Util.hs: -------------------------------------------------------------------------------- 1 | {- 2 | Copyright (C) 2009 John MacFarlane 3 | This program is free software; you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation; either version 2 of the License, or 6 | (at your option) any later version. 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | You should have received a copy of the GNU General Public License 12 | along with this program; if not, write to the Free Software 13 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 14 | -} 15 | 16 | {- Utility functions for Gitit. 17 | -} 18 | 19 | module Network.Gitit.Util ( readFileUTF8 20 | , inDir 21 | , withTempDir 22 | , orIfNull 23 | , splitCategories 24 | , trim 25 | , yesOrNo 26 | , parsePageType 27 | ) 28 | where 29 | import System.Directory 30 | import Control.Exception (bracket) 31 | import System.FilePath ((), (<.>)) 32 | import System.IO.Error (isAlreadyExistsError) 33 | import Control.Monad.Trans (liftIO) 34 | import Data.Char (toLower) 35 | import Data.ByteString.Lazy.UTF8 (toString) 36 | import qualified Data.ByteString.Lazy as B 37 | import Network.Gitit.Types 38 | import Control.Monad (liftM) 39 | import Codec.Binary.UTF8.String (encodeString) 40 | 41 | -- | Read file as UTF-8 string. Encode filename as UTF-8. 42 | readFileUTF8 :: FilePath -> IO String 43 | readFileUTF8 f = liftM toString $ B.readFile $ encodeString f 44 | 45 | -- | Perform a function a directory and return to working directory. 46 | inDir :: FilePath -> IO a -> IO a 47 | inDir d action = do 48 | w <- getCurrentDirectory 49 | setCurrentDirectory d 50 | result <- action 51 | setCurrentDirectory w 52 | return result 53 | 54 | -- | Perform a function in a temporary directory and clean up. 55 | withTempDir :: FilePath -> (FilePath -> IO a) -> IO a 56 | withTempDir baseName = bracket (createTempDir 0 baseName) (removeDirectoryRecursive) 57 | 58 | -- | Create a temporary directory with a unique name. 59 | createTempDir :: Integer -> FilePath -> IO FilePath 60 | createTempDir num baseName = do 61 | sysTempDir <- getTemporaryDirectory 62 | let dirName = sysTempDir baseName <.> show num 63 | liftIO $ catch (createDirectory dirName >> return dirName) $ 64 | \e -> if isAlreadyExistsError e 65 | then createTempDir (num + 1) baseName 66 | else ioError e 67 | 68 | -- | Returns a list, if it is not null, or a backup, if it is. 69 | orIfNull :: [a] -> [a] -> [a] 70 | orIfNull lst backup = if null lst then backup else lst 71 | 72 | -- | Split a string containing a list of categories. 73 | splitCategories :: String -> [String] 74 | splitCategories = words . map puncToSpace . trim 75 | where puncToSpace x | x `elem` ".,;:" = ' ' 76 | puncToSpace x = x 77 | 78 | -- | Trim leading and trailing spaces. 79 | trim :: String -> String 80 | trim = reverse . trimLeft . reverse . trimLeft 81 | where trimLeft = dropWhile (`elem` " \t") 82 | 83 | -- | Show Bool as "yes" or "no". 84 | yesOrNo :: Bool -> String 85 | yesOrNo True = "yes" 86 | yesOrNo False = "no" 87 | 88 | parsePageType :: String -> (PageType, Bool) 89 | parsePageType s = 90 | case map toLower s of 91 | "markdown" -> (Markdown,False) 92 | "markdown+lhs" -> (Markdown,True) 93 | "rst" -> (RST,False) 94 | "rst+lhs" -> (RST,True) 95 | "html" -> (HTML,False) 96 | "latex" -> (LaTeX,False) 97 | "latex+lhs" -> (LaTeX,True) 98 | x -> error $ "Unknown page type: " ++ x 99 | 100 | -------------------------------------------------------------------------------- /RELANN-0.6.1: -------------------------------------------------------------------------------- 1 | I'm pleased to announce the release of gitit 0.6.1. 2 | 3 | Gitit is a wiki program that runs on happstack, the Haskell web 4 | application server stack, and stores pages and other content in a 5 | git or darcs filestore. The wiki can be updated either directly 6 | through the VCS or through gitit's web interface. Pages can be written 7 | in (extended) markdown, reStructuredText, HTML, or LaTeX, and exported 8 | in ten different formats. TeX math is rendered using MathML by default, 9 | and syntax highlighting is provided for over fifty languages. 10 | 11 | demo: http://gitit.johnmacfarlane.net 12 | manual: http://gitit.johnmacfarlane.net/README 13 | api: http://hackage.haskell.org/package/gitit-0.6.1 14 | code: http://github.com/jgm/gitit 15 | bugs: http://code.google.com/p/gitit/issues/list 16 | group: http://groups.google.com/group/gitit-discuss 17 | 18 | Here is how you can install and run gitit. You'll need GHC and 19 | cabal-install. If you don't have these, install the Haskell Platform 20 | . Then: 21 | 22 | cabal update 23 | cabal install gitit 24 | mkdir mywiki 25 | cd mywiki 26 | gitit # now browse to http://localhost:5001 27 | 28 | Or, if you want to change the defaults (say, reStructuredText 29 | instead of markdown, or darcs instead of git): 30 | 31 | gitit --print-default-config > gitit.conf 32 | # edit gitit.conf, which is self-documenting 33 | gitit -f gitit.conf 34 | 35 | The whole code base has been overhauled since the last release. 36 | Gitit is now faster, more memory efficient, more modular, and more 37 | secure. It also has many new features, including 38 | 39 | - page metadata and categories 40 | - atom feeds (sitewide and per-page) 41 | - support for literate Haskell 42 | - a better configuration system 43 | - an improved caching system 44 | - a Haskell library exporting happstack wiki handlers 45 | - a plugin system 46 | 47 | The last two items are the most exciting and deserve special comment. 48 | 49 | First, in addition to providing an executable, gitit now provides a 50 | library, Network.Gitit, which makes it easy to include a gitit 51 | wiki (or many of them) in any happstack application. It is 52 | even possible to use the containing application's authentication 53 | system for the wiki. 54 | 55 | Second, gitit can now be extended through plugins, short Haskell 56 | programs that are loaded dynamically when the server starts. For 57 | examples of the things that can be done with plugins, see the 58 | plugins directory, which contains (among other things) a plugin 59 | for adding graphviz diagrams to pages and a plugin for adding 60 | interwiki links. For a full description of the plugin system, 61 | see the haddock documentation for Network.Gitit.Interface. 62 | 63 | Full changes from version 0.5.3, as well as upgrade instructions, 64 | are available in the file CHANGES. 65 | 66 | Thanks are due to 67 | 68 | - the happstack team, for big improvements in happstack-server 69 | that make it much easier to work with, 70 | 71 | - the darcs team, for using gitit/darcsit for , 72 | giving gitit a real-world test, 73 | 74 | - Gwern Branwen, who helped to optimize gitit, wrote the 75 | InterwikiPlugin, and wrote the guts of the Feed module, 76 | 77 | - Simon Michael, who contributed several patches, 78 | 79 | - Henry Laxen, who added support for password resets and helped with 80 | the apache proxy instructions, 81 | 82 | - Anton van Straaten, who made the process of page generation 83 | more modular by adding Gitit.ContentTransformer, 84 | 85 | - Robin Green, who helped improve the plugin API and interface, 86 | fixed a security problem with the reset password code, and made 87 | saving of the user's file more robust, 88 | 89 | - Thomas Hartman, who helped improve the index page, making directory 90 | browsing persistent, 91 | 92 | - Kohei Ozaki, who contributed the ImgTexPlugin, 93 | 94 | - mightybyte, who suggested making gitit available as a library, 95 | and contributed a patch to the authentication system, 96 | 97 | - and everyone else who contributed suggestions and bug reports. 98 | 99 | -------------------------------------------------------------------------------- /data/static/js/jquery.hotkeys-0.7.9.min.js: -------------------------------------------------------------------------------- 1 | (function(jQuery){jQuery.fn.__bind__=jQuery.fn.bind;jQuery.fn.__unbind__=jQuery.fn.unbind;jQuery.fn.__find__=jQuery.fn.find;var hotkeys={version:'0.7.9',override:/keypress|keydown|keyup/g,triggersMap:{},specialKeys:{27:'esc',9:'tab',32:'space',13:'return',8:'backspace',145:'scroll',20:'capslock',144:'numlock',19:'pause',45:'insert',36:'home',46:'del',35:'end',33:'pageup',34:'pagedown',37:'left',38:'up',39:'right',40:'down',109:'-',112:'f1',113:'f2',114:'f3',115:'f4',116:'f5',117:'f6',118:'f7',119:'f8',120:'f9',121:'f10',122:'f11',123:'f12',191:'/'},shiftNums:{"`":"~","1":"!","2":"@","3":"#","4":"$","5":"%","6":"^","7":"&","8":"*","9":"(","0":")","-":"_","=":"+",";":":","'":"\"",",":"<",".":">","/":"?","\\":"|"},newTrigger:function(type,combi,callback){var result={};result[type]={};result[type][combi]={cb:callback,disableInInput:false};return result;}};hotkeys.specialKeys=jQuery.extend(hotkeys.specialKeys,{96:'0',97:'1',98:'2',99:'3',100:'4',101:'5',102:'6',103:'7',104:'8',105:'9',106:'*',107:'+',109:'-',110:'.',111:'/'});jQuery.fn.find=function(selector){this.query=selector;return jQuery.fn.__find__.apply(this,arguments);};jQuery.fn.unbind=function(type,combi,fn){if(jQuery.isFunction(combi)){fn=combi;combi=null;} 2 | if(combi&&typeof combi==='string'){var selectorId=((this.prevObject&&this.prevObject.query)||(this[0].id&&this[0].id)||this[0]).toString();var hkTypes=type.split(' ');for(var x=0;x 20 | 21 | `*emphasized text*` 22 | *emphasized text* 23 | 24 | 25 | `**strong emphasis**` 26 | **strong emphasis** 27 | 28 | 29 | `` `literal text` `` 30 | `literal text` 31 | 32 | 33 | `\*escaped special characters\*` 34 | \*escaped special characters\* 35 | 36 | 37 | `[external link](http://google.com)` 38 | [external link](http://google.com) 39 | 40 | 41 | `![folder](/img/icons/folder.png)` 42 | ![folder](/img/icons/folder.png) 43 | 44 | 45 | Wikilink: `[Front Page]()` 46 | Wikilink: [Front Page]() 47 | 48 | 49 | `H~2~O` 50 | H~2~O 51 | 52 | 53 | `10^100^` 54 | 10^100^ 55 | 56 | 57 | `~~strikeout~~` 58 | ~~strikeout~~ 59 | 60 | 61 | 62 | `$x = \frac{{ - b \pm \sqrt {b^2 - 4ac} }}{{2a}}$` 63 | 64 | 65 | $x = \frac{{ - b \pm \sqrt {b^2 - 4ac} }}{{2a}}$^[If this looks like 66 | code, it's because jsMath is 67 | not installed on your system. Contact your administrator to request it.] 68 | 69 | 70 | 71 | 72 | `A simple footnote.^[Or is it so simple?]` 73 | 74 | 75 | A simple footnote.^[Or is it so simple?] 76 | 77 | 78 | 79 | 80 |
     81 | > an indented paragraph,
     82 | > usually used for quotations
     83 | 
    84 | 85 | 86 | 87 | > an indented paragraph, 88 | > usually used for quotations 89 | 90 | 91 | 92 | 93 |
     94 |     #!/bin/sh -e
     95 |     # code, indented four spaces
     96 |     echo "Hello world"
     97 | 
    98 | 99 | 100 | 101 | #!/bin/sh -e 102 | # code, indented four spaces 103 | echo "Hello world" 104 | 105 | 106 | 107 | 108 | 109 |
    110 | * a bulleted list
    111 | * second item
    112 |     - sublist
    113 |     - and more
    114 | * back to main list
    115 |     1. this item has an ordered
    116 |     2. sublist
    117 |         a) you can also use letters
    118 |         b) another item
    119 | 
    120 | 121 | 122 | 123 | * a bulleted list 124 | * second item 125 | - sublist 126 | - and more 127 | * back to main list 128 | 1. this item has an ordered 129 | 2. sublist 130 | a) you can also use letters 131 | b) another item 132 | 133 | 134 | 135 | 136 | 137 |
    138 | Fruit        Quantity
    139 | --------  -----------
    140 | apples         30,200
    141 | oranges         1,998
    142 | pears              42
    143 | 
    144 | Table:  Our fruit inventory
    145 | 
    146 | 147 | 148 | 149 | Fruit Quantity 150 | -------- ----------- 151 | apples 30,200 152 | oranges 1,998 153 | pears 42 154 | 155 | Table: Our fruit inventory 156 | 157 | 158 | 159 | 160 | 161 | For headings, prefix a line with one or more `#` signs: one for a major heading, 162 | two for a subheading, three for a subsubheading. Be sure to leave space before 163 | and after the heading. 164 | 165 | # Markdown 166 | 167 | Text... 168 | 169 | ## Some examples... 170 | 171 | Text... 172 | 173 | ## Wiki links 174 | 175 | Links to other wiki pages are formed this way: `[Page Name]()`. 176 | (Gitit converts markdown links with empty targets into wikilinks.) 177 | 178 | To link to a wiki page using something else as the link text: 179 | `[something else](Page Name)`. 180 | 181 | Note that page names may contain spaces and some special characters. 182 | They need not be CamelCase. CamelCase words are *not* automatically 183 | converted to wiki links. 184 | 185 | Wiki pages may be organized into directories. So, if you have 186 | several pages on wine, you may wish to organize them like so: 187 | 188 | Wine/Pinot Noir 189 | Wine/Burgundy 190 | Wine/Cabernet Sauvignon 191 | 192 | Note that a wiki link `[Burgundy]()` that occurs inside the `Wine` 193 | directory will link to `Wine/Burgundy`, and not to `Burgundy`. 194 | To link to a top-level page called `Burgundy`, you'd have to use 195 | `[Burgundy](/Burgundy)`. 196 | 197 | To link to a directory listing for a subdirectory, use a trailing 198 | slash: `[Wine/]()` will link to a listing of the `Wine` subdirectory. 199 | -------------------------------------------------------------------------------- /data/Help.page: -------------------------------------------------------------------------------- 1 | # Navigating 2 | 3 | The most natural way of navigating is by clicking wiki links that 4 | connect one page with another. The "Front page" link in the navigation 5 | bar will always take you to the Front Page of the wiki. The "All pages" 6 | link will take you to a list of all pages on the wiki (organized into 7 | folders if directories are used). Alternatively, you can search using 8 | the search box. Note that the search is set to look for whole words, so 9 | if you are looking for "gremlins", type that and not "gremlin". 10 | The "go" box will take you directly to the page you type. 11 | 12 | # Creating and modifying pages 13 | 14 | ## Registering for an account 15 | 16 | In order to modify pages, you'll need to be logged in. To register 17 | for an account, just click the "register" button in the bar on top 18 | of the screen. You'll be asked to choose a username and a password, 19 | which you can use to log in in the future by clicking the "login" 20 | button. While you are logged in, these buttons are replaced by 21 | a "logout so-and-so" button, which you should click to log out 22 | when you are finished. 23 | 24 | Note that logins are persistent through session cookies, so if you 25 | don't log out, you'll still be logged in when you return to the 26 | wiki from the same browser in the future. 27 | 28 | ## Editing a page 29 | 30 | To edit a page, just click the "edit" button at the bottom right corner 31 | of the page. 32 | 33 | You can click "Preview" at any time to see how your changes will look. 34 | Nothing is saved until you press "Save." 35 | 36 | Note that you must provide a description of your changes. This is to 37 | make it easier for others to see how a wiki page has been changed. 38 | 39 | ## Page metadata 40 | 41 | Pages may optionally begin with a metadata block. Here is an example: 42 | 43 | --- 44 | format: latex+lhs 45 | categories: haskell math 46 | toc: no 47 | title: Haskell and 48 | Category Theory 49 | ... 50 | 51 | \section{Why Category Theory?} 52 | 53 | The metadata block consists of a list of key-value pairs, each on a 54 | separate line. If needed, the value can be continued on one or more 55 | additional line, which must begin with a space. (This is illustrated by 56 | the "title" example above.) The metadata block must begin with a line 57 | `---` and end with a line `...` optionally followed by one or more blank 58 | lines. 59 | 60 | Currently the following keys are supported: 61 | 62 | format 63 | : Overrides the default page type as specified in the configuration file. 64 | Possible values are `markdown`, `rst`, `latex`, `html`, `markdown+lhs`, 65 | `rst+lhs`, `latex+lhs`. (Capitalization is ignored, so you can also 66 | use `LaTeX`, `HTML`, etc.) The `+lhs` variants indicate that the page 67 | is to be interpreted as literate Haskell. If this field is missing, 68 | the default page type will be used. 69 | 70 | categories 71 | : A space or comma separated list of categories to which the page belongs. 72 | 73 | toc 74 | : Overrides default setting for table-of-contents in the configuration file. 75 | Values can be `yes`, `no`, `true`, or `false` (capitalization is ignored). 76 | 77 | title 78 | : By default the displayed page title is the page name. This metadata element 79 | overrides that default. 80 | 81 | ## Creating a new page 82 | 83 | To create a new page, just create a wiki link that links to it, and 84 | click the link. If the page does not exist, you will be editing it 85 | immediately. 86 | 87 | ## Reverting to an earlier version 88 | 89 | If you click the "history" button at the bottom of the page, you will 90 | get a record of previous versions of the page. You can see the differences 91 | between two versions by dragging one onto the other; additions will be 92 | highlighted in yellow, and deletions will be crossed out with a horizontal 93 | line. Clicking on the description of changes will take you to the page 94 | as it existed after those changes. To revert the page to the revision 95 | you're currently looking at, just click the "revert" button at the bottom 96 | of the page, then "Save". 97 | 98 | ## Deleting a page 99 | 100 | The "delete" button at the bottom of the page will delete a page. Note 101 | that deleted pages can be recovered, since a record of them will still be 102 | accessible via the "activity" button on the top of the page. 103 | 104 | # Uploading files 105 | 106 | To upload a file--a picture, a PDF, or some other resource--click the 107 | "upload" button in the navigation bar. You will be prompted to select 108 | the file to upload. As with edits, you will be asked to provide a 109 | description of the resource (or of the change, if you are overwriting 110 | an existing file). 111 | 112 | Often you may leave "Name on wiki" blank, since the existing name of the 113 | file will be used by default. If that isn't desired, supply a name. 114 | Note that uploaded files *must* include a file extension (e.g. `.pdf`). 115 | 116 | If you are providing a new version of a file that already exists on the 117 | wiki, check the box "Overwrite existing file." Otherwise, leave it 118 | unchecked. 119 | 120 | To link to an uploaded file, just use its name in a regular wiki link. 121 | For example, if you uploaded a picture `fido.jpg`, you can insert the 122 | picture into a (markdown-formatted) page as follows: `![fido](fido.jpg)`. 123 | If you uploaded a PDF `projection.pdf`, you can insert a link to it 124 | using: `[projection](projection.pdf)`. 125 | 126 | -------------------------------------------------------------------------------- /Network/Gitit/State.hs: -------------------------------------------------------------------------------- 1 | {- 2 | Copyright (C) 2008 John MacFarlane 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program; if not, write to the Free Software 16 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | -} 18 | 19 | {- Functions for maintaining user list and session state. 20 | -} 21 | 22 | module Network.Gitit.State where 23 | 24 | import qualified Data.Map as M 25 | import System.Random (randomRIO) 26 | import Data.Digest.Pure.SHA (sha512, showDigest) 27 | import qualified Data.ByteString.Lazy.UTF8 as L (fromString) 28 | import qualified System.IO.Cautious as C (writeFile) 29 | import Data.IORef 30 | import System.IO.Unsafe (unsafePerformIO) 31 | import Control.Monad.Reader 32 | import Data.FileStore 33 | import Data.List (intercalate) 34 | import System.Log.Logger (Priority(..), logM) 35 | import Network.Gitit.Types 36 | 37 | gititstate :: IORef GititState 38 | gititstate = unsafePerformIO $ newIORef GititState { sessions = undefined 39 | , users = undefined 40 | , templatesPath = undefined 41 | , renderPage = undefined 42 | , plugins = undefined } 43 | 44 | updateGititState :: MonadIO m => (GititState -> GititState) -> m () 45 | updateGititState fn = liftIO $! atomicModifyIORef gititstate $ \st -> (fn st, ()) 46 | 47 | queryGititState :: MonadIO m => (GititState -> a) -> m a 48 | queryGititState fn = liftM fn $ liftIO $! readIORef gititstate 49 | 50 | debugMessage :: String -> GititServerPart () 51 | debugMessage msg = liftIO $ logM "gitit" DEBUG msg 52 | 53 | mkUser :: String -- username 54 | -> String -- email 55 | -> String -- unhashed password 56 | -> IO User 57 | mkUser uname email pass = do 58 | salt <- genSalt 59 | return User { uUsername = uname, 60 | uPassword = Password { pSalt = salt, 61 | pHashed = hashPassword salt pass }, 62 | uEmail = email } 63 | 64 | genSalt :: IO String 65 | genSalt = replicateM 32 $ randomRIO ('0','z') 66 | 67 | hashPassword :: String -> String -> String 68 | hashPassword salt pass = showDigest $ sha512 $ L.fromString $ salt ++ pass 69 | 70 | authUser :: String -> String -> GititServerPart Bool 71 | authUser name pass = do 72 | users' <- queryGititState users 73 | case M.lookup name users' of 74 | Just u -> do 75 | let salt = pSalt $ uPassword u 76 | let hashed = pHashed $ uPassword u 77 | return $ hashed == hashPassword salt pass 78 | Nothing -> return False 79 | 80 | isUser :: String -> GititServerPart Bool 81 | isUser name = liftM (M.member name) $ queryGititState users 82 | 83 | addUser :: String -> User -> GititServerPart () 84 | addUser uname user = 85 | updateGititState (\s -> s { users = M.insert uname user (users s) }) >> 86 | getConfig >>= 87 | liftIO . writeUserFile 88 | 89 | adjustUser :: String -> User -> GititServerPart () 90 | adjustUser uname user = updateGititState 91 | (\s -> s { users = M.adjust (const user) uname (users s) }) >> 92 | getConfig >>= 93 | liftIO . writeUserFile 94 | 95 | delUser :: String -> GititServerPart () 96 | delUser uname = 97 | updateGititState (\s -> s { users = M.delete uname (users s) }) >> 98 | getConfig >>= 99 | liftIO . writeUserFile 100 | 101 | writeUserFile :: Config -> IO () 102 | writeUserFile conf = do 103 | usrs <- queryGititState users 104 | C.writeFile (userFile conf) $ 105 | "[" ++ intercalate "\n," (map show $ M.toList usrs) ++ "\n]" 106 | 107 | getUser :: String -> GititServerPart (Maybe User) 108 | getUser uname = liftM (M.lookup uname) $ queryGititState users 109 | 110 | isSession :: MonadIO m => SessionKey -> m Bool 111 | isSession key = liftM (M.member key . unsession) $ queryGititState sessions 112 | 113 | setSession :: MonadIO m => SessionKey -> SessionData -> m () 114 | setSession key u = updateGititState $ \s -> 115 | s { sessions = Sessions . M.insert key u . unsession $ sessions s } 116 | 117 | newSession :: MonadIO m => SessionData -> m SessionKey 118 | newSession u = do 119 | key <- liftIO $ randomRIO (0, 1000000000) 120 | setSession key u 121 | return key 122 | 123 | delSession :: MonadIO m => SessionKey -> m () 124 | delSession key = updateGititState $ \s -> 125 | s { sessions = Sessions . M.delete key . unsession $ sessions s } 126 | 127 | getSession :: MonadIO m => SessionKey -> m (Maybe SessionData) 128 | getSession key = queryGititState $ M.lookup key . unsession . sessions 129 | 130 | getConfig :: GititServerPart Config 131 | getConfig = liftM wikiConfig ask 132 | 133 | getFileStore :: GititServerPart FileStore 134 | getFileStore = liftM wikiFileStore ask 135 | 136 | getDefaultPageType :: GititServerPart PageType 137 | getDefaultPageType = liftM defaultPageType getConfig 138 | 139 | getDefaultLHS :: GititServerPart Bool 140 | getDefaultLHS = liftM defaultLHS getConfig 141 | -------------------------------------------------------------------------------- /data/static/css/reset-fonts-grids.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009, Yahoo! Inc. All rights reserved. 3 | Code licensed under the BSD License: 4 | http://developer.yahoo.net/yui/license.txt 5 | version: 2.7.0 6 | MODIFIED by JM - don't reset list item styles, removed 'caption,th{text-align:left}' 7 | */ 8 | html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var,optgroup{font-style:inherit;font-weight:inherit;}del,ins{text-decoration:none;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:baseline;}sub{vertical-align:baseline;}legend{color:#000;}input,button,textarea,select,optgroup,option{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;}input,button,textarea,select{*font-size:100%;}body{font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}select,input,button,textarea,button{font:99% arial,helvetica,clean,sans-serif;}table{font-size:inherit;font:100%;}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%;}body{text-align:center;}#doc,#doc2,#doc3,#doc4,.yui-t1,.yui-t2,.yui-t3,.yui-t4,.yui-t5,.yui-t6,.yui-t7{margin:auto;text-align:left;width:57.69em;*width:56.25em;}#doc2{width:73.076em;*width:71.25em;}#doc3{margin:auto 10px;width:auto;}#doc4{width:74.923em;*width:73.05em;}.yui-b{position:relative;}.yui-b{_position:static;}#yui-main .yui-b{position:static;}#yui-main,.yui-g .yui-u .yui-g{width:100%;}.yui-t1 #yui-main,.yui-t2 #yui-main,.yui-t3 #yui-main{float:right;margin-left:-25em;}.yui-t4 #yui-main,.yui-t5 #yui-main,.yui-t6 #yui-main{float:left;margin-right:-25em;}.yui-t1 .yui-b{float:left;width:12.30769em;*width:12.00em;}.yui-t1 #yui-main .yui-b{margin-left:13.30769em;*margin-left:13.05em;}.yui-t2 .yui-b{float:left;width:13.8461em;*width:13.50em;}.yui-t2 #yui-main .yui-b{margin-left:14.8461em;*margin-left:14.55em;}.yui-t3 .yui-b{float:left;width:23.0769em;*width:22.50em;}.yui-t3 #yui-main .yui-b{margin-left:24.0769em;*margin-left:23.62em;}.yui-t4 .yui-b{float:right;width:13.8456em;*width:13.50em;}.yui-t4 #yui-main .yui-b{margin-right:14.8456em;*margin-right:14.55em;}.yui-t5 .yui-b{float:right;width:18.4615em;*width:18.00em;}.yui-t5 #yui-main .yui-b{margin-right:19.4615em;*margin-right:19.125em;}.yui-t6 .yui-b{float:right;width:23.0769em;*width:22.50em;}.yui-t6 #yui-main .yui-b{margin-right:24.0769em;*margin-right:23.62em;}.yui-t7 #yui-main .yui-b{display:block;margin:0 0 1em 0;}#yui-main .yui-b{float:none;width:auto;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf,.yui-gc .yui-u,.yui-gd .yui-g,.yui-g .yui-gc .yui-u,.yui-ge .yui-u,.yui-ge .yui-g,.yui-gf .yui-g,.yui-gf .yui-u{float:right;}.yui-g div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first,.yui-ge div.first,.yui-gf div.first,.yui-g .yui-gc div.first,.yui-g .yui-ge div.first,.yui-gc div.first div.first{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf{width:49.1%;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{width:32%;margin-left:1.99%;}.yui-gb .yui-u{*margin-left:1.9%;*width:31.9%;}.yui-gc div.first,.yui-gd .yui-u{width:66%;}.yui-gd div.first{width:32%;}.yui-ge div.first,.yui-gf .yui-u{width:74.2%;}.yui-ge .yui-u,.yui-gf div.first{width:24%;}.yui-g .yui-gb div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first{margin-left:0;}.yui-g .yui-g .yui-u,.yui-gb .yui-g .yui-u,.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u,.yui-ge .yui-g .yui-u,.yui-gf .yui-g .yui-u{width:49%;*width:48.1%;*margin-left:0;}.yui-g .yui-g .yui-u{width:48.1%;}.yui-g .yui-gb div.first,.yui-gb .yui-gb div.first{*margin-right:0;*width:32%;_width:31.7%;}.yui-g .yui-gc div.first,.yui-gd .yui-g{width:66%;}.yui-gb .yui-g div.first{*margin-right:4%;_margin-right:1.3%;}.yui-gb .yui-gc div.first,.yui-gb .yui-gd div.first{*margin-right:0;}.yui-gb .yui-gb .yui-u,.yui-gb .yui-gc .yui-u{*margin-left:1.8%;_margin-left:4%;}.yui-g .yui-gb .yui-u{_margin-left:1.0%;}.yui-gb .yui-gd .yui-u{*width:66%;_width:61.2%;}.yui-gb .yui-gd div.first{*width:31%;_width:29.5%;}.yui-g .yui-gc .yui-u,.yui-gb .yui-gc .yui-u{width:32%;_float:right;margin-right:0;_margin-left:0;}.yui-gb .yui-gc div.first{width:66%;*float:left;*margin-left:0;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf .yui-u{margin:0;}.yui-gb .yui-gb .yui-u{_margin-left:.7%;}.yui-gb .yui-g div.first,.yui-gb .yui-gb div.first{*margin-left:0;}.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u{*width:48.1%;*margin-left:0;}.yui-gb .yui-gd div.first{width:32%;}.yui-g .yui-gd div.first{_width:29.9%;}.yui-ge .yui-g{width:24%;}.yui-gf .yui-g{width:74.2%;}.yui-gb .yui-ge div.yui-u,.yui-gb .yui-gf div.yui-u{float:right;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf div.first{float:left;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf div.first{*width:24%;_width:20%;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf .yui-u{*width:73.5%;_width:65.5%;}.yui-ge div.first .yui-gd .yui-u{width:65%;}.yui-ge div.first .yui-gd div.first{width:32%;}#hd:after,#bd:after,#ft:after,.yui-g:after,.yui-gb:after,.yui-gc:after,.yui-gd:after,.yui-ge:after,.yui-gf:after{content:".";display:block;height:0;clear:both;visibility:hidden;}#hd,#bd,#ft,.yui-g,.yui-gb,.yui-gc,.yui-gd,.yui-ge,.yui-gf{zoom:1;} 9 | -------------------------------------------------------------------------------- /gitit.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {- 3 | Copyright (C) 2008 John MacFarlane 4 | 5 | This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program; if not, write to the Free Software 17 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | -} 19 | 20 | module Main where 21 | 22 | import Network.Gitit 23 | import Network.Gitit.Server 24 | import Network.Gitit.Util (readFileUTF8) 25 | import System.Directory 26 | import Data.Maybe (isNothing) 27 | import Control.Monad.Reader 28 | import System.Log.Logger (Priority(..), setLevel, setHandlers, 29 | getLogger, saveGlobalLogger) 30 | import System.Log.Handler.Simple (fileHandler) 31 | import System.Environment 32 | import System.Exit 33 | import System.IO (stderr) 34 | import System.Console.GetOpt 35 | import Data.Version (showVersion) 36 | import qualified Data.ByteString as B 37 | import Data.ByteString.UTF8 (fromString) 38 | 39 | import Paths_gitit (version, getDataFileName) 40 | 41 | main :: IO () 42 | main = do 43 | 44 | -- parse options to get config file 45 | opts <- getArgs >>= parseArgs 46 | defaultConfig <- getDefaultConfig 47 | conf <- foldM handleFlag defaultConfig opts 48 | -- check for external programs that are needed 49 | let repoProg = case repositoryType conf of 50 | Mercurial -> "hg" 51 | Darcs -> "darcs" 52 | Git -> "git" 53 | let prereqs = ["grep", repoProg] 54 | forM_ prereqs $ \prog -> 55 | findExecutable prog >>= \mbFind -> 56 | when (isNothing mbFind) $ error $ 57 | "Required program '" ++ prog ++ "' not found in system path." 58 | 59 | -- set up logging 60 | let level = if debugMode conf then DEBUG else logLevel conf 61 | logFileHandler <- fileHandler (logFile conf) level 62 | serverLogger <- getLogger "Happstack.Server.AccessLog.Combined" 63 | gititLogger <- getLogger "gitit" 64 | saveGlobalLogger $ setLevel level $ setHandlers [logFileHandler] serverLogger 65 | saveGlobalLogger $ setLevel level $ setHandlers [logFileHandler] gititLogger 66 | 67 | let conf' = conf{logLevel = level} 68 | 69 | -- setup the page repository, template, and static files, if they don't exist 70 | createRepoIfMissing conf' 71 | createStaticIfMissing conf' 72 | createTemplateIfMissing conf' 73 | 74 | -- initialize state 75 | initializeGititState conf' 76 | 77 | let serverConf = Conf { validator = Nothing, port = portNumber conf' } 78 | 79 | -- start the server 80 | simpleHTTP serverConf $ msum [ wiki conf' 81 | , dir "_reloadTemplates" reloadTemplates 82 | ] 83 | 84 | data Opt 85 | = Help 86 | | ConfigFile FilePath 87 | | Port Int 88 | | Debug 89 | | Version 90 | | PrintDefaultConfig 91 | deriving (Eq) 92 | 93 | flags :: [OptDescr Opt] 94 | flags = 95 | [ Option ['h'] ["help"] (NoArg Help) 96 | "Print this help message" 97 | , Option ['v'] ["version"] (NoArg Version) 98 | "Print version information" 99 | , Option ['p'] ["port"] (ReqArg (Port . read) "PORT") 100 | "Specify port" 101 | , Option [] ["print-default-config"] (NoArg PrintDefaultConfig) 102 | "Print default configuration" 103 | , Option [] ["debug"] (NoArg Debug) 104 | "Print debugging information on each request" 105 | , Option ['f'] ["config-file"] (ReqArg ConfigFile "FILE") 106 | "Specify configuration file" 107 | ] 108 | 109 | parseArgs :: [String] -> IO [Opt] 110 | parseArgs argv = do 111 | progname <- getProgName 112 | case getOpt Permute flags argv of 113 | (opts,_,[]) -> return opts 114 | (_,_,errs) -> putErr (ExitFailure 1) (concat errs ++ usageInfo (usageHeader progname) flags) 115 | 116 | usageHeader :: String -> String 117 | usageHeader progname = "Usage: " ++ progname ++ " [opts...]" 118 | 119 | copyrightMessage :: String 120 | copyrightMessage = "\nCopyright (C) 2008 John MacFarlane\n" ++ 121 | "This is free software; see the source for copying conditions. There is no\n" ++ 122 | "warranty, not even for merchantability or fitness for a particular purpose." 123 | 124 | compileInfo :: String 125 | compileInfo = 126 | #ifdef _PLUGINS 127 | " +plugins" 128 | #else 129 | " -plugins" 130 | #endif 131 | 132 | handleFlag :: Config -> Opt -> IO Config 133 | handleFlag conf opt = do 134 | progname <- getProgName 135 | case opt of 136 | Help -> putErr ExitSuccess (usageInfo (usageHeader progname) flags) 137 | Version -> putErr ExitSuccess (progname ++ " version " ++ showVersion version ++ compileInfo ++ copyrightMessage) 138 | PrintDefaultConfig -> getDataFileName "data/default.conf" >>= readFileUTF8 >>= B.putStrLn . fromString >> exitWith ExitSuccess 139 | Debug -> return conf{ debugMode = True } 140 | Port p -> return conf{ portNumber = p } 141 | ConfigFile fname -> getConfigFromFile fname 142 | 143 | putErr :: ExitCode -> String -> IO a 144 | putErr c s = B.hPutStrLn stderr (fromString s) >> exitWith c 145 | -------------------------------------------------------------------------------- /Network/Gitit/Feed.hs: -------------------------------------------------------------------------------- 1 | {- 2 | Copyright (C) 2009 Gwern Branwen and 3 | John MacFarlane 4 | 5 | This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program; if not, write to the Free Software 17 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | -} 19 | 20 | -- | Functions for creating Atom feeds for Gitit wikis and pages. 21 | 22 | module Network.Gitit.Feed (FeedConfig(..), filestoreToXmlFeed) where 23 | 24 | import Data.DateTime (addMinutes, formatDateTime, getCurrentTime) 25 | import Data.Foldable as F (concatMap) 26 | import Data.List (intercalate, sortBy, nub) 27 | import Data.Maybe (fromMaybe) 28 | import Data.Ord (comparing) 29 | import Network.URI (isUnescapedInURI, escapeURIString) 30 | import System.FilePath (dropExtension, takeExtension, (<.>)) 31 | import Data.FileStore.Types (history, Author(authorName), Change(..), 32 | DateTime, FileStore, Revision(..), TimeRange(..)) 33 | import Text.Atom.Feed (nullEntry, nullFeed, nullLink, nullPerson, 34 | Date, Entry(..), Feed(..), Link(linkRel), Generator(..), 35 | Person(personName), TextContent(TextString)) 36 | import Text.Atom.Feed.Export (xmlFeed) 37 | import Text.XML.Light (ppTopElement) 38 | import Data.Version (showVersion) 39 | import Paths_gitit (version) 40 | 41 | data FeedConfig = FeedConfig { 42 | fcTitle :: String 43 | , fcBaseUrl :: String 44 | , fcFeedDays :: Integer 45 | } deriving (Read, Show) 46 | 47 | gititGenerator :: Generator 48 | gititGenerator = Generator {genURI = Just "http://github.com/jgm/gitit" 49 | , genVersion = Just (showVersion version) 50 | , genText = "gitit"} 51 | 52 | filestoreToXmlFeed :: FeedConfig -> FileStore -> Maybe FilePath -> IO String 53 | filestoreToXmlFeed cfg f = fmap xmlFeedToString . generateFeed cfg gititGenerator f 54 | 55 | xmlFeedToString :: Feed -> String 56 | xmlFeedToString = ppTopElement . xmlFeed 57 | 58 | generateFeed :: FeedConfig -> Generator -> FileStore -> Maybe FilePath -> IO Feed 59 | generateFeed cfg generator fs mbPath = do 60 | now <- getCurrentTime 61 | revs <- changeLog (fcFeedDays cfg) fs mbPath now 62 | let home = fcBaseUrl cfg ++ "/" 63 | -- TODO: 'nub . sort' `persons` - but no Eq or Ord instances! 64 | persons = map authorToPerson $ nub $ sortBy (comparing authorName) $ map revAuthor revs 65 | basefeed = generateEmptyfeed generator (fcTitle cfg) home mbPath persons (formatFeedTime now) 66 | revisions = map (revisionToEntry home) revs 67 | return basefeed {feedEntries = revisions} 68 | 69 | -- | Get the last N days history. 70 | changeLog :: Integer -> FileStore -> Maybe FilePath ->DateTime -> IO [Revision] 71 | changeLog days a mbPath now' = do 72 | let files = F.concatMap (\f -> [f, f <.> "page"]) mbPath 73 | let startTime = addMinutes (-60 * 24 * days) now' 74 | rs <- history a files TimeRange{timeFrom = Just startTime, timeTo = Just now'} 75 | return $ sortBy (comparing revDateTime) rs 76 | 77 | generateEmptyfeed :: Generator -> String ->String ->Maybe String -> [Person] -> Date -> Feed 78 | generateEmptyfeed generator title home mbPath authors now = 79 | baseNull {feedAuthors = authors, 80 | feedGenerator = Just generator, 81 | feedLinks = [ (nullLink $ home ++ "_feed/" ++ escape (fromMaybe "" mbPath)) 82 | {linkRel = Just (Left "self")}] 83 | } 84 | where baseNull = nullFeed home (TextString title) now 85 | 86 | revisionToEntry :: String -> Revision -> Entry 87 | revisionToEntry home Revision{ revId = rid, revDateTime = rdt, 88 | revAuthor = ra, revDescription = rd, 89 | revChanges = rv} = 90 | baseEntry{ entrySummary = Just $ TextString rd 91 | , entryAuthors = [authorToPerson ra], entryLinks = [ln] } 92 | where baseEntry = nullEntry url (TextString (intercalate ", " $ map show rv)) 93 | (formatFeedTime rdt) 94 | url = home ++ escape (extract $ head rv) ++ "?revision=" ++ rid 95 | ln = (nullLink url) {linkRel = Just (Left "alternate")} 96 | 97 | -- gitit is set up not to reveal registration emails 98 | authorToPerson :: Author -> Person 99 | authorToPerson ra = nullPerson {personName = authorName ra} 100 | 101 | -- TODO: replace with Network.URI version of shortcut if it ever is added 102 | escape :: String -> String 103 | escape = escapeURIString isUnescapedInURI 104 | 105 | formatFeedTime :: DateTime -> String 106 | formatFeedTime = formatDateTime "%FT%TZ" 107 | 108 | -- TODO: this boilerplate can be removed by changing Data.FileStore.Types to say 109 | -- data Change = Modified {extract :: FilePath} | Deleted {extract :: FilePath} | Added 110 | -- {extract :: FilePath} 111 | -- so then it would be just 'escape (extract $ head rv)' without the 4 line definition 112 | extract :: Change -> FilePath 113 | extract x = dePage $ case x of {Modified n -> n; Deleted n -> n; Added n -> n} 114 | where dePage f = if takeExtension f == ".page" then dropExtension f else f 115 | 116 | -- TODO: figure out how to create diff links in a non-broken manner 117 | {- 118 | diff :: String -> String -> Revision -> Link 119 | diff home path' Revision{revId = rid} = 120 | let n = nullLink (home ++ "_diff/" ++ escape path' ++ "?to=" ++ rid) -- ++ fromrev) 121 | in n {linkRel = Just (Left "alternate")} 122 | -} 123 | -------------------------------------------------------------------------------- /Network/Gitit/Page.hs: -------------------------------------------------------------------------------- 1 | {- 2 | Copyright (C) 2009 John MacFarlane 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program; if not, write to the Free Software 16 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | -} 18 | 19 | {- Functions for translating between Page structures and raw 20 | - text strings. The strings may begin with a metadata block, 21 | - which looks like this (it is valid YAML): 22 | - 23 | - > --- 24 | - > title: Custom Title 25 | - > format: markdown+lhs 26 | - > toc: yes 27 | - > categories: foo bar baz 28 | - > ... 29 | - 30 | - This would tell gitit to use "Custom Title" as the displayed 31 | - page title (instead of the page name), to interpret the page 32 | - text as markdown with literate haskell, to include a table of 33 | - contents, and to include the page in the categories foo, bar, 34 | - and baz. 35 | - 36 | - The metadata block may be omitted entirely, and any particular line 37 | - may be omitted. The categories in the @categories@ field should be 38 | - separated by spaces. Commas will be treated as spaces. 39 | - 40 | - Metadata value fields may be continued on the next line, as long as 41 | - it is nonblank and starts with a space character. 42 | - 43 | - Unrecognized metadata fields are simply ignored. 44 | -} 45 | 46 | module Network.Gitit.Page ( stringToPage 47 | , pageToString 48 | , extractCategories 49 | ) 50 | where 51 | import Network.Gitit.Types 52 | import Network.Gitit.Util (trim, splitCategories, parsePageType) 53 | import Text.ParserCombinators.Parsec 54 | import Data.Char (toLower) 55 | import Data.List (intercalate) 56 | import Data.Maybe (fromMaybe) 57 | 58 | parseMetadata :: String -> ([(String, String)], String) 59 | parseMetadata raw = 60 | case parse pMetadataBlock "" raw of 61 | Left _ -> ([], raw) 62 | Right (ls, rest) -> (ls, rest) 63 | 64 | pMetadataBlock :: GenParser Char st ([(String, String)], String) 65 | pMetadataBlock = try $ do 66 | string "---" 67 | pBlankline 68 | ls <- many pMetadataLine 69 | string "..." 70 | pBlankline 71 | skipMany pBlankline 72 | rest <- getInput 73 | return (ls, rest) 74 | 75 | pBlankline :: GenParser Char st Char 76 | pBlankline = try $ many (oneOf " \t") >> newline 77 | 78 | pMetadataLine :: GenParser Char st (String, String) 79 | pMetadataLine = try $ do 80 | ident <- many1 letter 81 | skipMany (oneOf " \t") 82 | char ':' 83 | rawval <- many $ noneOf "\n\r" 84 | <|> (try $ newline >> notFollowedBy pBlankline >> 85 | skipMany1 (oneOf " \t") >> return ' ') 86 | newline 87 | return (ident, trim rawval) 88 | 89 | -- | Read a string (the contents of a page file) and produce a Page 90 | -- object, using defaults except when overridden by metadata. 91 | stringToPage :: Config -> String -> String -> Page 92 | stringToPage conf pagename raw = 93 | let (ls, rest) = parseMetadata raw 94 | page' = Page { pageName = pagename 95 | , pageFormat = defaultPageType conf 96 | , pageLHS = defaultLHS conf 97 | , pageTOC = tableOfContents conf 98 | , pageTitle = pagename 99 | , pageCategories = [] 100 | , pageText = filter (/= '\r') rest 101 | , pageMeta = ls } 102 | in foldr adjustPage page' ls 103 | 104 | adjustPage :: (String, String) -> Page -> Page 105 | adjustPage ("title", val) page' = page' { pageTitle = val } 106 | adjustPage ("format", val) page' = page' { pageFormat = pt, pageLHS = lhs } 107 | where (pt, lhs) = parsePageType val 108 | adjustPage ("toc", val) page' = page' { 109 | pageTOC = (map toLower val) `elem` ["yes","true"] } 110 | adjustPage ("categories", val) page' = 111 | page' { pageCategories = splitCategories val ++ pageCategories page' } 112 | adjustPage (_, _) page' = page' 113 | 114 | -- | Write a string (the contents of a page file) corresponding to 115 | -- a Page object, using explicit metadata only when needed. 116 | pageToString :: Config -> Page -> String 117 | pageToString conf page' = 118 | let pagename = pageName page' 119 | pagetitle = pageTitle page' 120 | pageformat = pageFormat page' 121 | pagelhs = pageLHS page' 122 | pagetoc = pageTOC page' 123 | pagecats = pageCategories page' 124 | metadata' = (if pagename /= pagetitle 125 | then "!title: " ++ pagetitle ++ "\n" 126 | else "") ++ 127 | (if pageformat /= defaultPageType conf || 128 | pagelhs /= defaultLHS conf 129 | then "!format: " ++ 130 | map toLower (show pageformat) ++ 131 | if pagelhs then "+lhs\n" else "\n" 132 | else "") ++ 133 | (if pagetoc /= tableOfContents conf 134 | then "!toc: " ++ 135 | (if pagetoc then "yes" else "no") ++ "\n" 136 | else "") ++ 137 | (if not (null pagecats) 138 | then "!categories: " ++ intercalate " " pagecats ++ "\n" 139 | else "") 140 | in metadata' ++ (if null metadata' then "" else "\n") ++ pageText page' 141 | 142 | extractCategories :: String -> [String] 143 | extractCategories s | take 3 s == "---" = 144 | let (md,_) = parseMetadata s 145 | in splitCategories $ fromMaybe "" $ lookup "categories" md 146 | extractCategories _ = [] 147 | -------------------------------------------------------------------------------- /data/static/css/screen.css: -------------------------------------------------------------------------------- 1 | @import url("reset-fonts-grids.css"); 2 | 3 | html { background: #f9f9f9; color: black; } 4 | body { margin: 10px; } 5 | 6 | fieldset { border: 1px solid #ccc; padding: 1em; } 7 | legend { font-weight: bold; margin-left: 1em; padding: 4px; } 8 | textarea, input[type='text'], input[type='password'], select { border: 1px solid #ccc; background: #fff; } 9 | textarea:hover, input[type='text']:hover, input[type='password']:hover, select:hover { border-color: #aaa; } 10 | textarea:focus, input[type='text']:focus, input[type='password']:focus, select:focus { border-color: #888; outline: 2px solid #ffffaa; } 11 | input, select { cursor: pointer; } 12 | input[type='text'] { cursor: text; } 13 | textarea, input { padding: .3em .4em .15em .4em; } 14 | select { padding: .1em .2em 0 .2em; } 15 | option { padding: 0 .4em; } 16 | table, tr, td, th { border: none; } 17 | hr { height: 1px; color: #aaa; background-color: #aaa; border: 0; margin: .2em 0 .2em 0; } 18 | 19 | h1, h2, h3, h4, h5, h6 { font-weight: normal; border-bottom: 1px solid black; } 20 | 21 | h1.pageTitle { font-size: 197%; margin: 0.2em 0 .5em; } 22 | 23 | h1 { font-size: 153.9%; margin: 1.07em 0 .535em; } 24 | h2 { font-size: 138.5%; margin: 1.14em 0 .57em; } 25 | h3 { font-size: 123.1%; margin: 1.23em 0 .615em; } 26 | h4 { font-size: 116%; margin: 1.33em 0 .67em; } 27 | h5 { font-size: 108%; margin: 1.6em 0 .8em; } 28 | h6 { font-size: 100%; margin: 1.6em 0 .8em; } 29 | 30 | strong { font-weight: bold; } 31 | ul { list-style-type: square; } 32 | dt { font-weight: bold; margin-bottom: .1em; } 33 | 34 | optgroup{ font-weight: normal; } 35 | abbr, acronym { border-bottom:1px dotted #000; cursor:help; } 36 | em { font-style: italic; } 37 | del { text-decoration: line-through; } 38 | 39 | blockquote, ul, ol, dl { margin: 1em; } 40 | ol, ul, dl { margin-left: 2em; } 41 | dl dd { margin-left: 1em; } 42 | th, td { padding: .5em; } 43 | th { font-weight: bold; } 44 | caption { margin-bottom: .5em; text-align: center; } 45 | sup { vertical-align: super; } 46 | sub { vertical-align: sub; } 47 | p, fieldset, table, pre { margin-bottom: 1em; } 48 | button, input[type="checkbox"], input[type="radio"], input[type="reset"], input[type="submit"] { padding: 1px; } 49 | 50 | blockquote { padding: 0 1.6em; color: #666; } 51 | 52 | a:link { text-decoration: underline; color: #36c; } 53 | a:visited { text-decoration: underline; color: #99c; } 54 | a:hover { text-decoration: underline; color: #c33; } 55 | a:active, a:focus { text-decoration: underline; color: #000; } 56 | 57 | input.search_term { width: 95% } 58 | 59 | button:hover, a.button:hover { background-color:#dff4ff; border:1px solid #c2e1ef; color:#336699; } 60 | a.button:active, button:active { background-color:#6299c5; border:1px solid #6299c5; color:#fff; } 61 | 62 | h1 > a:link, h1 > a:active, h1 > a:hover, h1 > a:focus, h1 > a:visited, 63 | h2 > a:link, h2 > a:active, h2 > a:hover, h2 > a:focus, h2 > a:visited, 64 | h3 > a:link, h3 > a:active, h3 > a:hover, h3 > a:focus, h3 > a:visited, 65 | h4 > a:link, h4 > a:active, h4 > a:hover, h4 > a:focus, h4 > a:visited, 66 | h5 > a:link, h5 > a:active, h5 > a:hover, h5 > a:focus, h5 > a:visited, 67 | h6 > a:link, h6 > a:active, h6 > a:hover, h6 > a:focus, h6 > a:visited { 68 | color: black; text-decoration: none; } 69 | 70 | #content { border: 1px solid #ccc; background-color: #fff; padding: 1em; font-size: 108%; } 71 | #content p, #content pre, #content li { line-height: 140%; } 72 | 73 | #userbox { text-align: right; font-weight: bold; margin: 1em; } 74 | 75 | #logo { min-height: 50px; } 76 | 77 | #sidebar fieldset { background-color: white; margin-bottom: 1em; padding: 0; font-size: 93%; } 78 | #sidebar fieldset, #sidebar fieldset legend { font-weight: normal; } 79 | #sidebar ul { padding: 0; margin: 0; margin-left: 1.6em; line-height: 1.5em; } 80 | #sidebar ul li { color: #888; list-style: square; } 81 | 82 | div#toc { background-color: #f9f9f9; border: 10px solid white; margin: 0.8em; margin-right: 0; padding: 0.4em; } 83 | #toc ul { padding: 0 0 0 1em; margin: 0; list-style: none; } 84 | 85 | #sidebar input, #sidebar select { font-size: 93%; padding: 0.1em; } 86 | #sidebar input[type='submit'] { border: none; background-color: #ccc; color: white; } 87 | 88 | #exportbox select { width: 8.5em; border: 1px solid #ccc; padding: 0; } 89 | #exportbox { margin: 0.3em 0 0.5em 0.4em; padding: 0; } 90 | 91 | #footer { padding: 1em; color: #888; text-align: center; font-size: 93%; } 92 | 93 | #searchform { padding: 0; margin: 0.3em 0 0.5em 0.4em; } 94 | #searchform input[type='text'] { width: 8.5em; border: 1px solid #ccc; } 95 | 96 | div#categoryList { padding: 0em; margin: 1em 0 0 0; border: 1px dashed #ccc; } 97 | #categoryList ul > li { display: inline; padding-right: 1em; } 98 | 99 | #editform textarea { height: 25em; width: 98%; font-family: monospace; font-size: 93%; } 100 | #editform #logMsg { width: 98%; margin-right: 1em; margin-bottom: 0.3em; } 101 | 102 | #goform { padding: 0; margin: 0.3em 0 0.5em 0.4em; } 103 | #goform input[type='text'] { width: 8.5em; border: 1px solid #ccc; } 104 | 105 | .search_result { margin-bottom: 15px; } 106 | .search_result .match { margin-bottom: 15px; } 107 | 108 | code { font-size: 93%; } 109 | pre.matches { margin: 0; padding: 0; } 110 | #pattern { background-color: yellow; font-weight: bold; } 111 | pre.matches span.highlighted { background-color: yellow; } 112 | 113 | .added { background-color: yellow; } 114 | .deleted { text-decoration: line-through; color: gray; } 115 | 116 | /* .req is used to hide a honeypot in a form */ 117 | .req { display: none; } 118 | 119 | ul.messages > li { color: red; list-style: square; font-weight: bold; } 120 | 121 | ul.tabs { padding: 0; margin: 0 0 1px 0; } 122 | ul.tabs li { display: inline; border: 1px solid #ccc; border-bottom: none; padding: 0 0.6em 0 0.6em; 123 | margin: 0 0 0 1.2em; background: white; } 124 | ul.tabs li.selected { border-bottom: 3px solid white; } 125 | ul.tabs li a { text-decoration: none; font-size: 93%; font-weight: bold; margin: 0; color: #36c; } 126 | 127 | .index ul { list-style: none; margin: 0; padding: 0; } 128 | .index li { list-style: none; background-position: 0 1px; background-repeat: no-repeat; padding-left: 20px; } 129 | .index li.page { background-image: url(../img/icons/page.png); } 130 | .index li.folder { background-image: url(../img/icons/folder.png); } 131 | .index a { color: #000000; cursor: pointer; text-decoration: none; } 132 | .index a:hover { text-decoration: underline; } 133 | 134 | a.updir { font-weight: bold; } 135 | 136 | h2.revision { font-size: 100%; color: #888; font-style: italic; border: none; margin: 0 0 0.5em 0; padding: 0; } 137 | 138 | div.markupHelp pre { font-size: 77%; overflow: auto; } 139 | 140 | .login { display: none; } 141 | -------------------------------------------------------------------------------- /Network/Gitit/Layout.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleContexts #-} 2 | {- 3 | Copyright (C) 2009 John MacFarlane 4 | 5 | This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program; if not, write to the Free Software 17 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | -} 19 | 20 | {- Functions and data structures for wiki page layout. 21 | -} 22 | 23 | module Network.Gitit.Layout ( defaultPageLayout 24 | , defaultRenderPage 25 | , formattedPage 26 | , filledPageTemplate 27 | , uploadsAllowed 28 | ) 29 | where 30 | import Network.Gitit.Server 31 | import Network.Gitit.Framework 32 | import Network.Gitit.State 33 | import Network.Gitit.Types 34 | import Network.Gitit.Export (exportFormats) 35 | import Network.HTTP (urlEncodeVars) 36 | import qualified Text.StringTemplate as T 37 | import Prelude hiding (catch) 38 | import Text.XHtml hiding ( (), dir, method, password, rev ) 39 | import Text.XHtml.Strict ( stringToHtmlString ) 40 | import Data.Maybe (isNothing, isJust, fromJust) 41 | 42 | defaultPageLayout :: PageLayout 43 | defaultPageLayout = PageLayout 44 | { pgPageName = "" 45 | , pgRevision = Nothing 46 | , pgPrintable = False 47 | , pgMessages = [] 48 | , pgTitle = "" 49 | , pgScripts = [] 50 | , pgShowPageTools = True 51 | , pgShowSiteNav = True 52 | , pgMarkupHelp = Nothing 53 | , pgTabs = [ViewTab, EditTab, HistoryTab, DiscussTab] 54 | , pgSelectedTab = ViewTab 55 | , pgLinkToFeed = False 56 | } 57 | 58 | -- | Returns formatted page 59 | formattedPage :: PageLayout -> Html -> Handler 60 | formattedPage layout htmlContents = do 61 | renderer <- queryGititState renderPage 62 | renderer layout htmlContents 63 | 64 | -- | Given a compiled string template, returns a page renderer. 65 | defaultRenderPage :: T.StringTemplate String -> PageLayout -> Html -> Handler 66 | defaultRenderPage templ layout htmlContents = do 67 | cfg <- getConfig 68 | base' <- getWikiBase 69 | ok . setContentType "text/html; charset=utf-8" . toResponse . T.render . 70 | filledPageTemplate base' cfg layout htmlContents $ templ 71 | 72 | -- | Returns a page template with gitit variables filled in. 73 | filledPageTemplate :: String -> Config -> PageLayout -> Html -> 74 | T.StringTemplate String -> T.StringTemplate String 75 | filledPageTemplate base' cfg layout htmlContents templ = 76 | let rev = pgRevision layout 77 | page = pgPageName layout 78 | scripts = ["jquery.min.js", "jquery-ui.packed.js"] ++ pgScripts layout 79 | scriptLink x = script ! [src (base' ++ "/js/" ++ x), 80 | thetype "text/javascript"] << noHtml 81 | javascriptlinks = renderHtmlFragment $ concatHtml $ map scriptLink scripts 82 | tabli tab = if tab == pgSelectedTab layout 83 | then li ! [theclass "selected"] 84 | else li 85 | tabs' = [x | x <- pgTabs layout, 86 | not (x == EditTab && page `elem` noEdit cfg)] 87 | tabs = ulist ! [theclass "tabs"] << map (linkForTab tabli base' page rev) tabs' 88 | setStrAttr attr = T.setAttribute attr . stringToHtmlString 89 | setBoolAttr attr test = if test then T.setAttribute attr "true" else id 90 | in T.setAttribute "base" base' . 91 | T.setAttribute "feed" (pgLinkToFeed layout) . 92 | setStrAttr "wikititle" (wikiTitle cfg) . 93 | setStrAttr "pagetitle" (pgTitle layout) . 94 | T.setAttribute "javascripts" javascriptlinks . 95 | setStrAttr "pagename" page . 96 | setStrAttr "pageUrl" (urlForPage page) . 97 | setBoolAttr "ispage" (isPage page) . 98 | setBoolAttr "pagetools" (pgShowPageTools layout) . 99 | setBoolAttr "sitenav" (pgShowSiteNav layout) . 100 | maybe id (T.setAttribute "markuphelp") (pgMarkupHelp layout) . 101 | setBoolAttr "printable" (pgPrintable layout) . 102 | maybe id (T.setAttribute "revision") rev . 103 | T.setAttribute "exportbox" 104 | (renderHtmlFragment $ exportBox base' cfg page rev) . 105 | T.setAttribute "tabs" (renderHtmlFragment tabs) . 106 | T.setAttribute "messages" (pgMessages layout) . 107 | T.setAttribute "usecache" (useCache cfg) . 108 | T.setAttribute "content" (renderHtmlFragment htmlContents) . 109 | setBoolAttr "wikiupload" ( uploadsAllowed cfg) $ 110 | templ 111 | 112 | 113 | 114 | exportBox :: String -> Config -> String -> Maybe String -> Html 115 | exportBox base' cfg page rev | not (isSourceCode page) = 116 | gui (base' ++ urlForPage page) ! [identifier "exportbox"] << 117 | ([ textfield "revision" ! [thestyle "display: none;", 118 | value (fromJust rev)] | isJust rev ] ++ 119 | [ select ! [name "format"] << 120 | map ((\f -> option ! [value f] << f) . fst) (exportFormats cfg) 121 | , primHtmlChar "nbsp" 122 | , submit "export" "Export" ]) 123 | exportBox _ _ _ _ = noHtml 124 | 125 | -- auxiliary functions: 126 | 127 | linkForTab :: (Tab -> Html -> Html) -> String -> String -> Maybe String -> Tab -> Html 128 | linkForTab tabli base' page _ HistoryTab = 129 | tabli HistoryTab << anchor ! [href $ base' ++ "/_history" ++ urlForPage page] << "history" 130 | linkForTab tabli _ _ _ DiffTab = 131 | tabli DiffTab << anchor ! [href ""] << "diff" 132 | linkForTab tabli base' page rev ViewTab = 133 | let origPage s = if isDiscussPage s 134 | then drop 1 s 135 | else s 136 | in if isDiscussPage page 137 | then tabli DiscussTab << anchor ! 138 | [href $ base' ++ urlForPage (origPage page)] << "page" 139 | else tabli ViewTab << anchor ! 140 | [href $ base' ++ urlForPage page ++ 141 | case rev of 142 | Just r -> "?revision=" ++ r 143 | Nothing -> ""] << "view" 144 | linkForTab tabli base' page _ DiscussTab = 145 | tabli (if isDiscussPage page then ViewTab else DiscussTab) << 146 | anchor ! [href $ base' ++ if isDiscussPage page then "" else "/_discuss" ++ 147 | urlForPage page] << "discuss" 148 | linkForTab tabli base' page rev EditTab = 149 | tabli EditTab << anchor ! 150 | [href $ base' ++ "/_edit" ++ urlForPage page ++ 151 | case rev of 152 | Just r -> "?revision=" ++ r ++ "&" ++ 153 | urlEncodeVars [("logMsg", "Revert to " ++ r)] 154 | Nothing -> ""] << if isNothing rev 155 | then "edit" 156 | else "revert" 157 | 158 | uploadsAllowed :: Config -> Bool 159 | uploadsAllowed = (0 <) . maxUploadSize 160 | -------------------------------------------------------------------------------- /Network/Gitit/Interface.hs: -------------------------------------------------------------------------------- 1 | {- 2 | Copyright (C) 2009 John MacFarlane 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program; if not, write to the Free Software 16 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | -} 18 | 19 | {- | Interface for plugins. 20 | 21 | A plugin is a Haskell module that is dynamically loaded by gitit. 22 | 23 | There are three kinds of plugins: 'PageTransform's, 24 | 'PreParseTransform's, and 'PreCommitTransform's. These plugins differ 25 | chiefly in where they are applied. 'PreCommitTransform' plugins are 26 | applied just before changes to a page are saved and may transform 27 | the raw source that is saved. 'PreParseTransform' plugins are applied 28 | when a page is viewed and may alter the raw page source before it 29 | is parsed as a 'Pandoc' document. Finally, 'PageTransform' plugins 30 | modify the 'Pandoc' document that results after a page's source is 31 | parsed, but before it is converted to HTML: 32 | 33 | > +--------------------------+ 34 | > | edited text from browser | 35 | > +--------------------------+ 36 | > || <---- PreCommitTransform plugins 37 | > \/ 38 | > || <---- saved to repository 39 | > \/ 40 | > +---------------------------------+ 41 | > | raw page source from repository | 42 | > +---------------------------------+ 43 | > || <---- PreParseTransform plugins 44 | > \/ 45 | > || <---- markdown or RST reader 46 | > \/ 47 | > +-----------------+ 48 | > | Pandoc document | 49 | > +-----------------+ 50 | > || <---- PageTransform plugins 51 | > \/ 52 | > +---------------------+ 53 | > | new Pandoc document | 54 | > +---------------------+ 55 | > || <---- HTML writer 56 | > \/ 57 | > +----------------------+ 58 | > | HTML version of page | 59 | > +----------------------+ 60 | 61 | Note that 'PreParseTransform' and 'PageTransform' plugins do not alter 62 | the page source stored in the repository. They only affect what is 63 | visible on the website. Only 'PreCommitTransform' plugins can 64 | alter what is stored in the repository. 65 | 66 | Note also that 'PreParseTransform' and 'PageTransform' plugins will 67 | not be run when the cached version of a page is used. Plugins can 68 | use the 'doNotCache' command to prevent a page from being cached, 69 | if their behavior is sensitive to things that might change from 70 | one time to another (such as the time or currently logged-in user). 71 | 72 | You can use the helper functions 'mkPageTransform' and 'mkPageTransformM' 73 | to create 'PageTransform' plugins from a transformation of any 74 | of the basic types used by Pandoc (for example, @Inline@, @Block@, 75 | @[Inline]@, even @String@). Here is a simple (if silly) example: 76 | 77 | > -- Deprofanizer.hs 78 | > module Deprofanizer (plugin) where 79 | > 80 | > -- This plugin replaces profane words with "XXXXX". 81 | > 82 | > import Network.Gitit.Interface 83 | > import Data.Char (toLower) 84 | > 85 | > plugin :: Plugin 86 | > plugin = mkPageTransform deprofanize 87 | > 88 | > deprofanize :: Inline -> Inline 89 | > deprofanize (Str x) | isBadWord x = Str "XXXXX" 90 | > deprofanize x = x 91 | > 92 | > isBadWord :: String -> Bool 93 | > isBadWord x = (map toLower x) `elem` ["darn", "blasted", "stinker"] 94 | > -- there are more, but this is a family program 95 | 96 | Further examples can be found in the @plugins@ directory in 97 | the source distribution. If you have installed gitit using Cabal, 98 | you can also find them in the directory 99 | @CABALDIR\/share\/gitit-X.Y.Z\/plugins@, where @CABALDIR@ is the cabal 100 | install directory and @X.Y.Z@ is the version number of gitit. 101 | 102 | -} 103 | 104 | module Network.Gitit.Interface ( Plugin(..) 105 | , PluginM 106 | , mkPageTransform 107 | , mkPageTransformM 108 | , Config(..) 109 | , Request(..) 110 | , User(..) 111 | , Context(..) 112 | , PageType(..) 113 | , PageLayout(..) 114 | , askConfig 115 | , askUser 116 | , askRequest 117 | , askFileStore 118 | , askMeta 119 | , doNotCache 120 | , getContext 121 | , modifyContext 122 | , inlinesToURL 123 | , inlinesToString 124 | , liftIO 125 | , withTempDir 126 | , module Text.Pandoc.Definition 127 | ) 128 | where 129 | import Text.Pandoc.Definition 130 | import Data.Data 131 | import Network.Gitit.Types 132 | import Network.Gitit.ContentTransformer 133 | import Network.Gitit.Util (withTempDir) 134 | import Network.Gitit.Server (Request(..)) 135 | import Control.Monad.Reader (ask) 136 | import Control.Monad.Trans (liftIO) 137 | import Control.Monad (liftM) 138 | import Data.FileStore (FileStore) 139 | 140 | -- | Returns the current wiki configuration. 141 | askConfig :: PluginM Config 142 | askConfig = liftM pluginConfig ask 143 | 144 | -- | Returns @Just@ the logged in user, or @Nothing@ if nobody is logged in. 145 | askUser :: PluginM (Maybe User) 146 | askUser = liftM pluginUser ask 147 | 148 | -- | Returns the complete HTTP request. 149 | askRequest :: PluginM Request 150 | askRequest = liftM pluginRequest ask 151 | 152 | -- | Returns the wiki filestore. 153 | askFileStore :: PluginM FileStore 154 | askFileStore = liftM pluginFileStore ask 155 | 156 | -- | Returns the page meta data 157 | askMeta :: PluginM [(String, String)] 158 | askMeta = liftM ctxMeta getContext 159 | 160 | -- | Indicates that the current page or file is not to be cached. 161 | doNotCache :: PluginM () 162 | doNotCache = modifyContext (\ctx -> ctx{ ctxCacheable = False }) 163 | 164 | -- | Lifts a function from @a -> a@ (for example, @Inline -> Inline@, 165 | -- @Block -> Block@, @[Inline] -> [Inline]@, or @String -> String@) 166 | -- to a 'PageTransform' plugin. 167 | mkPageTransform :: Data a => (a -> a) -> Plugin 168 | mkPageTransform fn = PageTransform $ return . processWith fn 169 | 170 | -- | Monadic version of 'mkPageTransform'. 171 | -- Lifts a function from @a -> m a@ to a 'PageTransform' plugin. 172 | mkPageTransformM :: Data a => (a -> PluginM a) -> Plugin 173 | mkPageTransformM = PageTransform . processWithM 174 | 175 | -------------------------------------------------------------------------------- /gitit.cabal: -------------------------------------------------------------------------------- 1 | name: gitit 2 | version: 0.7.3.8 3 | Cabal-version: >= 1.2 4 | build-type: Simple 5 | synopsis: Wiki using happstack, git or darcs, and pandoc. 6 | description: Gitit is a wiki backed by a git, darcs, or mercurial 7 | filestore. Pages and uploaded files can be modified either 8 | directly via the VCS's command-line tools or through 9 | the wiki's web interface. Pandoc is used for markup 10 | processing, so pages may be written in 11 | (extended) markdown, reStructuredText, LaTeX, HTML, 12 | or literate Haskell, and exported in ten different 13 | formats, including LaTeX, ConTeXt, DocBook, RTF, 14 | OpenOffice ODT, and MediaWiki markup. 15 | . 16 | Notable features include 17 | . 18 | * plugins: dynamically loaded page 19 | transformations written in Haskell (see 20 | "Network.Gitit.Interface") 21 | . 22 | * conversion of TeX math to MathML for display in 23 | web browsers 24 | . 25 | * syntax highlighting of source code 26 | files and code snippets 27 | . 28 | * Atom feeds (site-wide and per-page) 29 | . 30 | * a library, "Network.Gitit", that makes it simple 31 | to include a gitit wiki in any happstack application 32 | . 33 | You can see a running demo at . 34 | . 35 | For usage information: @gitit --help@ 36 | 37 | category: Network 38 | license: GPL 39 | license-file: LICENSE 40 | author: John MacFarlane 41 | maintainer: jgm@berkeley.edu 42 | bug-reports: http://code.google.com/p/gitit/issues/list 43 | homepage: http://github.com/jgm/gitit/tree/master 44 | stability: experimental 45 | data-files: data/static/css/screen.css, data/static/css/print.css, 46 | data/static/css/ie.css, data/static/css/hk-pyg.css, 47 | data/static/css/reset-fonts-grids.css, 48 | data/static/css/custom.css, 49 | data/static/img/logo.png, data/static/img/icons/feed.png, 50 | data/static/img/icons/folder.png, data/static/img/icons/page.png, 51 | data/static/js/dragdiff.js, data/static/js/jquery.min.js, 52 | data/static/js/uploadForm.js, data/static/js/jquery-ui.packed.js, 53 | data/static/js/jquery.hotkeys-0.7.9.min.js, 54 | data/static/js/preview.js, data/static/js/search.js, 55 | data/static/js/MathMLinHTML.js, 56 | data/static/robots.txt, 57 | data/post-update, data/FrontPage.page, data/Help.page, 58 | data/markup.Markdown, data/markup.RST, 59 | data/markup.HTML, data/markup.LaTeX, 60 | data/default.conf, 61 | data/templates/page.st, data/templates/content.st, 62 | data/templates/userbox.st, data/templates/footer.st, 63 | data/templates/logo.st, data/templates/markuphelp.st, 64 | data/templates/pagetools.st, data/templates/sitenav.st, 65 | data/templates/messages.st, data/templates/listitem.st, 66 | data/templates/expire.st, data/templates/getuser.st, 67 | data/markupHelp/Markdown, data/markupHelp/Markdown+LHS, 68 | data/markupHelp/RST, data/markupHelp/RST+LHS, 69 | data/markupHelp/LaTeX, data/markupHelp/LaTeX+LHS, 70 | data/markupHelp/HTML, 71 | plugins/CapitalizeEmphasis.hs, 72 | plugins/PigLatin.hs, 73 | plugins/Dot.hs, 74 | plugins/ImgTex.hs, 75 | plugins/Interwiki.hs, 76 | plugins/Deprofanizer.hs, 77 | plugins/WebArchiver.hs, 78 | plugins/ShowUser.hs, 79 | plugins/Signature.hs, 80 | plugins/Subst.hs, 81 | CHANGES, README.markdown, YUI-LICENSE, BLUETRIP-LICENSE, TANGOICONS 82 | 83 | Flag plugins 84 | description: Compile in support for plugins. This will increase the size of 85 | the executable and the memory it uses, so those who will not need 86 | plugins should disable this flag. 87 | default: True 88 | 89 | Library 90 | hs-source-dirs: . 91 | exposed-modules: Network.Gitit, Network.Gitit.ContentTransformer, 92 | Network.Gitit.Types, Network.Gitit.Framework, 93 | Network.Gitit.Initialize, Network.Gitit.Config, 94 | Network.Gitit.Layout 95 | other-modules: Network.Gitit.Cache, Network.Gitit.State, 96 | Paths_gitit, Network.Gitit.Server, Network.Gitit.Export, 97 | Network.Gitit.Util, Network.Gitit.Handlers, Network.Gitit.Plugins, 98 | Network.Gitit.Authentication, Network.Gitit.Page, Network.Gitit.Feed 99 | if flag(plugins) 100 | exposed-modules: Network.Gitit.Interface 101 | build-depends: ghc, ghc-paths 102 | cpp-options: -D_PLUGINS 103 | build-depends: base >= 3, pandoc >= 1.6 && < 1.7, filepath, safe 104 | extensions: CPP 105 | if impl(ghc >= 6.12) 106 | ghc-options: -Wall -fno-warn-unused-do-bind 107 | else 108 | ghc-options: -Wall 109 | ghc-prof-options: -auto-all -caf-all 110 | 111 | Executable gitit 112 | hs-source-dirs: . 113 | main-is: gitit.hs 114 | build-depends: base >=3 && < 5, parsec, pretty, xhtml, containers, 115 | pandoc >= 1.6 && < 1.7, process, filepath, directory, mtl, cgi, 116 | network, old-time, highlighting-kate >= 0.2.7.1, bytestring, 117 | utf8-string >= 0.3 && < 0.4, 118 | SHA > 1 && < 1.5, HTTP >= 4000.0 && < 4000.2, 119 | HStringTemplate >= 0.6 && < 0.7, random, 120 | network >= 2.1.0.0 && < 2.4, 121 | recaptcha >= 0.1, filestore >= 0.3.4.3, 122 | datetime >= 0.1 && < 0.3, zlib >= 0.5 && < 0.6, 123 | url >= 2.1 && < 2.2, 124 | happstack-server >= 0.5 && < 0.6, 125 | happstack-util >= 0.5 && < 0.6, xml >= 1.3.5, 126 | hslogger >= 1 && < 1.2, ConfigFile >= 1 && < 1.1, 127 | feed >= 0.3.6 && < 0.4, 128 | cautious-file >= 0.1.5 && < 0.2, 129 | xss-sanitize >= 0.2 && < 0.3 130 | if impl(ghc >= 6.10) 131 | build-depends: base >= 4, syb 132 | if flag(plugins) 133 | build-depends: ghc, ghc-paths 134 | cpp-options: -D_PLUGINS 135 | extensions: CPP 136 | if impl(ghc >= 6.12) 137 | ghc-options: -Wall -threaded -fno-warn-unused-do-bind 138 | else 139 | ghc-options: -Wall -threaded 140 | ghc-prof-options: -auto-all -caf-all 141 | 142 | Executable expireGititCache 143 | hs-source-dirs: . 144 | main-is: expireGititCache.hs 145 | build-depends: base >=3 && < 5, HTTP, url, filepath 146 | if impl(ghc >= 6.10) 147 | build-depends: base >= 4, syb 148 | ghc-options: -Wall 149 | 150 | -------------------------------------------------------------------------------- /Network/Gitit.hs: -------------------------------------------------------------------------------- 1 | {- 2 | Copyright (C) 2009 John MacFarlane 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program; if not, write to the Free Software 16 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | -} 18 | 19 | {- | Functions for embedding a gitit wiki into a Happstack application. 20 | 21 | The following is a minimal standalone wiki program: 22 | 23 | > import Network.Gitit 24 | > import Happstack.Server.SimpleHTTP 25 | > 26 | > main = do 27 | > conf <- getDefaultConfig 28 | > createStaticIfMissing conf 29 | > createTemplateIfMissing conf 30 | > createRepoIfMissing conf 31 | > initializeGititState conf 32 | > simpleHTTP nullConf{port = 5001} $ wiki conf 33 | 34 | Here is a more complex example, which serves different wikis 35 | under different paths, and uses a custom authentication scheme: 36 | 37 | > import Network.Gitit 38 | > import Control.Monad 39 | > import Text.XHtml hiding (dir) 40 | > import Happstack.Server.SimpleHTTP 41 | > 42 | > type WikiSpec = (String, FileStoreType, PageType) 43 | > 44 | > wikis = [ ("markdownWiki", Git, Markdown) 45 | > , ("latexWiki", Darcs, LaTeX) ] 46 | > 47 | > -- custom authentication 48 | > myWithUser :: Handler -> Handler 49 | > myWithUser handler = do 50 | > -- replace the following with a function that retrieves 51 | > -- the logged in user for your happstack app: 52 | > user <- return "testuser" 53 | > localRq (setHeader "REMOTE_USER" user) handler 54 | > 55 | > myAuthHandler = msum 56 | > [ dir "_login" $ seeOther "/your/login/url" $ toResponse () 57 | > , dir "_logout" $ seeOther "/your/logout/url" $ toResponse () ] 58 | > 59 | > handlerFor :: Config -> WikiSpec -> ServerPart Response 60 | > handlerFor conf (path', fstype, pagetype) = dir path' $ 61 | > wiki conf{ repositoryPath = path' 62 | > , repositoryType = fstype 63 | > , defaultPageType = pagetype} 64 | > 65 | > indexPage :: ServerPart Response 66 | > indexPage = ok $ toResponse $ 67 | > (p << "Wiki index") +++ 68 | > ulist << map (\(path', _, _) -> li << hotlink (path' ++ "/") << path') wikis 69 | > 70 | > main = do 71 | > conf <- getDefaultConfig 72 | > let conf' = conf{authHandler = myAuthHandler, withUser = myWithUser} 73 | > forM wikis $ \(path', fstype, pagetype) -> do 74 | > let conf'' = conf'{ repositoryPath = path' 75 | > , repositoryType = fstype 76 | > , defaultPageType = pagetype 77 | > } 78 | > createStaticIfMissing conf'' 79 | > createRepoIfMissing conf'' 80 | > createTemplateIfMissing conf' 81 | > initializeGititState conf' 82 | > simpleHTTP nullConf{port = 5001} $ 83 | > (nullDir >> indexPage) `mplus` msum (map (handlerFor conf') wikis) 84 | 85 | -} 86 | 87 | module Network.Gitit ( 88 | -- * Wiki handlers 89 | wiki 90 | , reloadTemplates 91 | , runHandler 92 | -- * Initialization 93 | , module Network.Gitit.Initialize 94 | -- * Configuration 95 | , module Network.Gitit.Config 96 | , loginUserForm 97 | -- * Types 98 | , module Network.Gitit.Types 99 | -- * Tools for building handlers 100 | , module Network.Gitit.Framework 101 | , module Network.Gitit.Layout 102 | , module Network.Gitit.ContentTransformer 103 | , module Network.Gitit.Page 104 | , getFileStore 105 | , getUser 106 | , getConfig 107 | , queryGititState 108 | , updateGititState 109 | ) 110 | where 111 | import Network.Gitit.Types 112 | import Network.Gitit.Server 113 | import Network.Gitit.Framework 114 | import Network.Gitit.Handlers 115 | import Network.Gitit.Initialize 116 | import Network.Gitit.Config 117 | import Network.Gitit.Layout 118 | import Network.Gitit.State 119 | (getFileStore, getUser, getConfig, queryGititState, updateGititState) 120 | import Network.Gitit.ContentTransformer 121 | import Network.Gitit.Page 122 | import Network.Gitit.Authentication (loginUserForm) 123 | import Paths_gitit (getDataFileName) 124 | import Control.Monad.Reader 125 | import Prelude hiding (readFile) 126 | import qualified Data.ByteString.Char8 as B 127 | import System.FilePath (()) 128 | import Safe 129 | 130 | -- | Happstack handler for a gitit wiki. 131 | wiki :: Config -> ServerPart Response 132 | wiki conf = do 133 | let static = staticDir conf 134 | defaultStatic <- liftIO $ getDataFileName $ "data" "static" 135 | -- if file not found in staticDir, we check also in the data/static 136 | -- directory, which contains defaults 137 | let staticHandler = withExpiresHeaders $ 138 | fileServeStrict' [] static `mplus` fileServeStrict' [] defaultStatic 139 | let handlers = [debugHandler | debugMode conf] ++ (authHandler conf : wikiHandlers) 140 | let fs = filestoreFromConfig conf 141 | let ws = WikiState { wikiConfig = conf, wikiFileStore = fs } 142 | if compressResponses conf 143 | then compressedResponseFilter 144 | else return "" 145 | staticHandler `mplus` runHandler ws (withUser conf $ msum handlers) 146 | 147 | -- | Like 'fileServeStrict', but if file is not found, fail instead of 148 | -- returning a 404 error. 149 | fileServeStrict' :: [FilePath] -> FilePath -> ServerPart Response 150 | fileServeStrict' ps p = do 151 | rq <- askRq 152 | resp <- fileServeStrict ps p 153 | if rsCode resp == 404 || lastNote "fileServeStrict'" (rqUri rq) == '/' 154 | then mzero -- pass through if not found or directory index 155 | else do 156 | -- turn off compresion filter unless it's text 157 | case getHeader "Content-Type" resp of 158 | Just ct | B.pack "text/" `B.isPrefixOf` ct -> return resp 159 | _ -> ignoreFilters >> return resp 160 | 161 | wikiHandlers :: [Handler] 162 | wikiHandlers = 163 | [ -- redirect /wiki -> /wiki/ when gitit is being served at /wiki 164 | -- so that relative wikilinks on the page will work properly: 165 | guardBareBase >> getWikiBase >>= \b -> movedPermanently (b ++ "/") (toResponse ()) 166 | , dir "_user" currentUser 167 | , dir "_activity" showActivity 168 | , dir "_go" goToPage 169 | , dir "_search" searchResults 170 | , dir "_upload" $ do guard =<< return . uploadsAllowed =<< getConfig 171 | msum [ methodOnly GET >> requireUser uploadForm 172 | , methodOnly POST >> requireUser uploadFile ] 173 | , dir "_random" $ methodOnly GET >> randomPage 174 | , dir "_index" indexPage 175 | , dir "_feed" feedHandler 176 | , dir "_category" categoryPage 177 | , dir "_categories" categoryListPage 178 | , dir "_expire" expireCache 179 | , dir "_showraw" $ msum 180 | [ showRawPage 181 | , guardPath isSourceCode >> showFileAsText ] 182 | , dir "_history" $ msum 183 | [ showPageHistory 184 | , guardPath isSourceCode >> showFileHistory ] 185 | , dir "_edit" $ requireUser (unlessNoEdit editPage showPage) 186 | , dir "_diff" $ msum 187 | [ showPageDiff 188 | , guardPath isSourceCode >> showFileDiff ] 189 | , dir "_discuss" discussPage 190 | , dir "_delete" $ msum 191 | [ methodOnly GET >> 192 | requireUser (unlessNoDelete confirmDelete showPage) 193 | , methodOnly POST >> 194 | requireUser (unlessNoDelete deletePage showPage) ] 195 | , dir "_preview" preview 196 | , guardIndex >> indexPage 197 | , guardCommand "export" >> exportPage 198 | , methodOnly POST >> guardCommand "cancel" >> showPage 199 | , methodOnly POST >> guardCommand "update" >> 200 | requireUser (unlessNoEdit updatePage showPage) 201 | , showPage 202 | , guardPath isSourceCode >> methodOnly GET >> showHighlightedSource 203 | , handleAny 204 | , notFound =<< (guardPath isPage >> createPage) 205 | ] 206 | 207 | -- | Recompiles the gitit templates. 208 | reloadTemplates :: ServerPart Response 209 | reloadTemplates = do 210 | liftIO recompilePageTemplate 211 | ok $ toResponse "Page templates have been recompiled." 212 | 213 | -- | Converts a gitit Handler into a standard happstack ServerPart. 214 | runHandler :: WikiState -> Handler -> ServerPart Response 215 | runHandler = mapServerPartT . unpackReaderT 216 | 217 | unpackReaderT:: (Monad m) 218 | => c 219 | -> (ReaderT c m) (Maybe ((Either b a), FilterFun b)) 220 | -> m (Maybe ((Either b a), FilterFun b)) 221 | unpackReaderT st handler = runReaderT handler st 222 | -------------------------------------------------------------------------------- /Network/Gitit/Initialize.hs: -------------------------------------------------------------------------------- 1 | {- 2 | Copyright (C) 2009 John MacFarlane 3 | This program is free software; you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation; either version 2 of the License, or 6 | (at your option) any later version. 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | You should have received a copy of the GNU General Public License 12 | along with this program; if not, write to the Free Software 13 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 14 | -} 15 | 16 | {- | Functions for initializing a Gitit wiki. 17 | -} 18 | 19 | module Network.Gitit.Initialize ( initializeGititState 20 | , recompilePageTemplate 21 | , compilePageTemplate 22 | , createStaticIfMissing 23 | , createRepoIfMissing 24 | , createDefaultPages 25 | , createTemplateIfMissing ) 26 | where 27 | import System.FilePath ((), (<.>)) 28 | import Data.FileStore 29 | import qualified Data.Map as M 30 | import Network.Gitit.Util (readFileUTF8) 31 | import Network.Gitit.Types 32 | import Network.Gitit.State 33 | import Network.Gitit.Framework 34 | import Network.Gitit.Plugins 35 | import Network.Gitit.Layout (defaultRenderPage) 36 | import Paths_gitit (getDataFileName) 37 | import Control.Exception (throwIO, try) 38 | import System.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExist, doesFileExist) 39 | import Control.Monad (unless, forM_, liftM) 40 | import Text.Pandoc 41 | import System.Log.Logger (logM, Priority(..)) 42 | import qualified Text.StringTemplate as T 43 | 44 | -- | Initialize Gitit State. 45 | initializeGititState :: Config -> IO () 46 | initializeGititState conf = do 47 | let userFile' = userFile conf 48 | pluginModules' = pluginModules conf 49 | plugins' <- loadPlugins pluginModules' 50 | 51 | userFileExists <- doesFileExist userFile' 52 | users' <- if userFileExists 53 | then liftM (M.fromList . read) $ readFileUTF8 userFile' 54 | else return M.empty 55 | 56 | templ <- compilePageTemplate (templatesDir conf) 57 | 58 | updateGititState $ \s -> s { sessions = Sessions M.empty 59 | , users = users' 60 | , templatesPath = templatesDir conf 61 | , renderPage = defaultRenderPage templ 62 | , plugins = plugins' } 63 | 64 | -- | Recompile the page template. 65 | recompilePageTemplate :: IO () 66 | recompilePageTemplate = do 67 | tempsDir <- queryGititState templatesPath 68 | ct <- compilePageTemplate tempsDir 69 | updateGititState $ \st -> st{renderPage = defaultRenderPage ct} 70 | 71 | --- | Compile a master page template named @page.st@ in the directory specified. 72 | compilePageTemplate :: FilePath -> IO (T.StringTemplate String) 73 | compilePageTemplate tempsDir = do 74 | defaultGroup <- getDataFileName ("data" "templates") >>= T.directoryGroup 75 | customExists <- doesDirectoryExist tempsDir 76 | combinedGroup <- 77 | if customExists 78 | -- default templates from data directory will be "shadowed" 79 | -- by templates from the user's template dir 80 | then do customGroup <- T.directoryGroup tempsDir 81 | return $ T.mergeSTGroups customGroup defaultGroup 82 | else do logM "gitit" WARNING $ "Custom template directory not found" 83 | return defaultGroup 84 | case T.getStringTemplate "page" combinedGroup of 85 | Just t -> return t 86 | Nothing -> error "Could not get string template" 87 | 88 | -- | Create templates dir if it doesn't exist. 89 | createTemplateIfMissing :: Config -> IO () 90 | createTemplateIfMissing conf' = do 91 | templateExists <- doesDirectoryExist (templatesDir conf') 92 | unless templateExists $ do 93 | createDirectoryIfMissing True (templatesDir conf') 94 | templatePath <- getDataFileName $ "data" "templates" 95 | -- templs <- liftM (filter (`notElem` [".",".."])) $ 96 | -- getDirectoryContents templatePath 97 | -- Copy footer.st, since this is the component users 98 | -- are most likely to want to customize: 99 | forM_ ["footer.st"] $ \t -> do 100 | copyFile (templatePath t) (templatesDir conf' t) 101 | logM "gitit" WARNING $ "Created " ++ (templatesDir conf' t) 102 | 103 | -- | Create page repository unless it exists. 104 | createRepoIfMissing :: Config -> IO () 105 | createRepoIfMissing conf = do 106 | let fs = filestoreFromConfig conf 107 | repoExists <- try (initialize fs) >>= \res -> 108 | case res of 109 | Right _ -> do 110 | logM "gitit" WARNING $ "Created repository in " ++ repositoryPath conf 111 | return False 112 | Left RepositoryExists -> return True 113 | Left e -> throwIO e >> return False 114 | unless repoExists $ createDefaultPages conf 115 | 116 | createDefaultPages :: Config -> IO () 117 | createDefaultPages conf = do 118 | let fs = filestoreFromConfig conf 119 | pt = defaultPageType conf 120 | toPandoc = readMarkdown 121 | defaultParserState{ stateSmart = True } 122 | defOpts = defaultWriterOptions{ 123 | writerStandalone = False 124 | , writerHTMLMathMethod = JsMath 125 | (Just "/js/jsMath/easy/load.js") 126 | , writerLiterateHaskell = showLHSBirdTracks conf 127 | } 128 | -- note: we convert this (markdown) to the default page format 129 | converter = case defaultPageType conf of 130 | Markdown -> id 131 | LaTeX -> writeLaTeX defOpts . toPandoc 132 | HTML -> writeHtmlString defOpts . toPandoc 133 | RST -> writeRST defOpts . toPandoc 134 | 135 | welcomepath <- getDataFileName $ "data" "FrontPage" <.> "page" 136 | welcomecontents <- liftM converter $ readFileUTF8 welcomepath 137 | helppath <- getDataFileName $ "data" "Help" <.> "page" 138 | helpcontentsInitial <- liftM converter $ readFileUTF8 helppath 139 | markuppath <- getDataFileName $ "data" "markup" <.> show pt 140 | helpcontentsMarkup <- liftM converter $ readFileUTF8 markuppath 141 | let helpcontents = helpcontentsInitial ++ "\n\n" ++ helpcontentsMarkup 142 | usersguidepath <- getDataFileName "README.markdown" 143 | usersguidecontents <- liftM converter $ readFileUTF8 usersguidepath 144 | -- add front page, help page, and user's guide 145 | let auth = Author "Gitit" "" 146 | createIfMissing fs (frontPage conf <.> "page") auth "Default front page" welcomecontents 147 | createIfMissing fs "Help.page" auth "Default help page" helpcontents 148 | createIfMissing fs "Gitit User's Guide.page" auth "User's guide (README)" usersguidecontents 149 | 150 | createIfMissing :: FileStore -> FilePath -> Author -> Description -> String -> IO () 151 | createIfMissing fs p a comm cont = do 152 | res <- try $ create fs p a comm cont 153 | case res of 154 | Right _ -> logM "gitit" WARNING ("Added " ++ p ++ " to repository") 155 | Left ResourceExists -> return () 156 | Left e -> throwIO e >> return () 157 | 158 | -- | Create static directory unless it exists. 159 | createStaticIfMissing :: Config -> IO () 160 | createStaticIfMissing conf = do 161 | let staticdir = staticDir conf 162 | staticExists <- doesDirectoryExist staticdir 163 | unless staticExists $ do 164 | 165 | let cssdir = staticdir "css" 166 | createDirectoryIfMissing True cssdir 167 | cssDataDir <- getDataFileName $ "data" "static" "css" 168 | -- cssFiles <- liftM (filter (\f -> takeExtension f == ".css")) $ getDirectoryContents cssDataDir 169 | forM_ ["custom.css"] $ \f -> do 170 | copyFile (cssDataDir f) (cssdir f) 171 | logM "gitit" WARNING $ "Created " ++ (cssdir f) 172 | 173 | {- 174 | let icondir = staticdir "img" "icons" 175 | createDirectoryIfMissing True icondir 176 | iconDataDir <- getDataFileName $ "data" "static" "img" "icons" 177 | iconFiles <- liftM (filter (\f -> takeExtension f == ".png")) $ getDirectoryContents iconDataDir 178 | forM_ iconFiles $ \f -> do 179 | copyFile (iconDataDir f) (icondir f) 180 | logM "gitit" WARNING $ "Created " ++ (icondir f) 181 | -} 182 | 183 | logopath <- getDataFileName $ "data" "static" "img" "logo.png" 184 | createDirectoryIfMissing True $ staticdir "img" 185 | copyFile logopath $ staticdir "img" "logo.png" 186 | logM "gitit" WARNING $ "Created " ++ (staticdir "img" "logo.png") 187 | 188 | {- 189 | let jsdir = staticdir "js" 190 | createDirectoryIfMissing True jsdir 191 | jsDataDir <- getDataFileName $ "data" "static" "js" 192 | javascripts <- liftM (filter (`notElem` [".", ".."])) $ getDirectoryContents jsDataDir 193 | forM_ javascripts $ \f -> do 194 | copyFile (jsDataDir f) (jsdir f) 195 | logM "gitit" WARNING $ "Created " ++ (jsdir f) 196 | -} 197 | 198 | -------------------------------------------------------------------------------- /data/default.conf: -------------------------------------------------------------------------------- 1 | # gitit wiki configuration file 2 | 3 | port: 5001 4 | # sets the port on which the web server will run. 5 | 6 | wiki-title: Wiki 7 | # the title of the wiki. 8 | 9 | repository-type: Git 10 | # specifies the type of repository used for wiki content. 11 | # Options are Git, Darcs, and Mercurial. 12 | 13 | repository-path: wikidata 14 | # specifies the path of the repository directory. If it does not 15 | # exist, gitit will create it on startup. 16 | 17 | authentication-method: form 18 | # 'form' means that users will be logged in and registered 19 | # using forms in the gitit web interface. 'http' means 20 | # that gitit will assume that HTTP authentication is in 21 | # place and take the logged in username from the "Authorization" 22 | # field of the HTTP request header (in addition, 23 | # the login/logout and registration links will be 24 | # suppressed). 'generic' means that gitit will assume that 25 | # some form of authentication is in place that directly 26 | # sets REMOTE_USER to the name of the authenticated user 27 | # (e.g. mod_auth_cas on apache). 28 | 29 | user-file: gitit-users 30 | # specifies the path of the file containing user login information. 31 | # If it does not exist, gitit will create it (with an empty user list). 32 | # This file is not used if 'http' is selected for authentication-method. 33 | 34 | session-timeout: 60 35 | # number of minutes of inactivity before a session expires. 36 | 37 | static-dir: static 38 | # specifies the path of the static directory (containing javascript, 39 | # css, and images). If it does not exist, gitit will create it 40 | # and populate it with required scripts, stylesheets, and images. 41 | 42 | default-page-type: Markdown 43 | # specifies the type of markup used to interpret pages in the wiki. 44 | # Possible values are Markdown, RST, LaTeX, HTML, Markdown+LHS, RST+LHS, 45 | # and LaTeX+LHS. (The +LHS variants treat the input as 46 | # literate Haskell. See pandoc's documentation for more details.) If 47 | # Markdown is selected, pandoc's syntax extensions (for footnotes, 48 | # delimited code blocks, etc.) will be enabled. Note that pandoc's 49 | # reStructuredText parser is not complete, so some pages may not be 50 | # rendered correctly if RST is selected. The same goes for LaTeX and 51 | # HTML. 52 | 53 | math: MathML 54 | # specifies how LaTeX math is to be displayed. Possible values 55 | # are MathML, raw, jsMath, and google. If mathml is selected, gitit will 56 | # convert LaTeX math to MathML and link in a script, MathMLinHTML.js, 57 | # that allows the MathML to be seen in Gecko browsers, IE + 58 | # mathplayer, and Opera. In other browsers you may get a jumble 59 | # of characters. If raw is selected, the LaTeX math will be displayed 60 | # as raw LaTeX math. If jsMath is selected, gitit will link to 61 | # the script /js/jsMath/easy/load.js, and will assume that jsMath 62 | # has been installed into the js/jsMath directory. This is the most 63 | # portable solution. If google is selected, the google chart API is 64 | # called to render the formula as an image. This requires a connection 65 | # to google, and might raise a technical or a privacy problem. 66 | 67 | show-lhs-bird-tracks: no 68 | # specifies whether to show Haskell code blocks in "bird style", 69 | # with "> " at the beginning of each line. 70 | 71 | templates-dir: templates 72 | # specifies the path of the directory containing page templates. 73 | # If it does not exist, gitit will create it with default templates. 74 | # Users may wish to edit the templates to customize the appearance of 75 | # their wiki. The template files are HStringTemplate templates. 76 | # Variables to be interpolated appear between $'s. Literal $'s must be 77 | # backslash-escaped. 78 | 79 | log-file: gitit.log 80 | # specifies the path of gitit's log file. If it does not exist, 81 | # gitit will create it. The log is in Apache combined log format. 82 | 83 | log-level: WARNING 84 | # determines how much information is logged. 85 | # Possible values (from most to least verbose) are DEBUG, INFO, 86 | # NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY. 87 | 88 | front-page: Front Page 89 | # specifies which wiki page is to be used as the wiki's front page. 90 | # Gitit creates a default front page on startup, if one does not exist 91 | # already. 92 | 93 | no-delete: Front Page, Help 94 | # specifies pages that cannot be deleted through the web interface. 95 | # (They can still be deleted directly using git or darcs.) 96 | # A comma-separated list of page names. Leave blank to allow 97 | # every page to be deleted. 98 | 99 | no-edit: Help 100 | # specifies pages that cannot be edited through the web interface. 101 | # Leave blank to allow every page to be edited. 102 | 103 | default-summary: 104 | # specifies text to be used in the change description if the author 105 | # leaves the "description" field blank. If default-summary is blank 106 | # (the default), the author will be required to fill in the description 107 | # field. 108 | 109 | table-of-contents: yes 110 | # specifies whether to print a tables of contents (with links to 111 | # sections) on each wiki page. 112 | 113 | plugins: 114 | # specifies a list of plugins to load. Plugins may be specified 115 | # either by their path or by their module name. If the plugin name 116 | # starts with Gitit.Plugin., gitit will assume that the plugin is 117 | # an installed module and will not try to find a source file. 118 | # Examples: 119 | # plugins: plugins/DotPlugin.hs, CapitalizeEmphasisPlugin.hs 120 | # plugins: plugins/DotPlugin 121 | # plugins: Gitit.Plugin.InterwikiLinks 122 | 123 | use-cache: no 124 | # specifies whether to cache rendered pages. Note that if use-feed 125 | # is selected, feeds will be cached regardless of the value of use-cache. 126 | 127 | cache-dir: cache 128 | # directory where rendered pages will be cached 129 | 130 | max-upload-size: 100K 131 | # specifies an upper limit on the size (in bytes) of files uploaded 132 | # through the wiki's web interface. 133 | # To disable uploads, set this to 0K. 134 | # This will result in the uploads link disappearing 135 | # and the _upload url becoming inactive. 136 | 137 | max-page-size: 100K 138 | # specifies an upper limit on the size (in bytes) of pages 139 | 140 | debug-mode: no 141 | # if "yes", causes debug information to be logged while gitit is running. 142 | 143 | compress-responses: yes 144 | # specifies whether HTTP responses should be compressed. 145 | 146 | mime-types-file: /etc/mime.types 147 | # specifies the path of a file containing mime type mappings. 148 | # Each line of the file should contain two fields, separated by 149 | # whitespace. The first field is the mime type, the second is a 150 | # file extension. For example: 151 | # video/x-ms-wmx wmx 152 | # If the file is not found, some simple defaults will be used. 153 | 154 | use-recaptcha: no 155 | # if "yes", causes gitit to use the reCAPTCHA service 156 | # (http://recaptcha.net) to prevent bots from creating accounts. 157 | 158 | recaptcha-private-key: 159 | recaptcha-public-key: 160 | # specifies the public and private keys for the reCAPTCHA service. 161 | # To get these, you need to create an account at http://recaptcha.net. 162 | 163 | access-question: 164 | access-question-answers: 165 | # specifies a question that users must answer when they attempt to create 166 | # an account, along with a comma-separated list of acceptable answers. 167 | # This can be used to institute a rudimentary password for signing up as 168 | # a user on the wiki, or as an alternative to reCAPTCHA. 169 | # Example: 170 | # access-question: What is the code given to you by Ms. X? 171 | # access-question-answers: RED DOG, red dog 172 | 173 | mail-command: sendmail %s 174 | # specifies the command to use to send notification emails. 175 | # '%s' will be replaced by the destination email address. 176 | # The body of the message will be read from stdin. 177 | # If this field is left blank, password reset will not be offered. 178 | 179 | reset-password-message: 180 | > From: nobody@$hostname$ 181 | > To: $useremail$ 182 | > Subject: Wiki password reset 183 | > 184 | > Dear $username$: 185 | > 186 | > To reset your password, please follow the link below: 187 | > http://$hostname$:$port$$resetlink$ 188 | > 189 | > Yours sincerely, 190 | > The Wiki Master 191 | 192 | # gives the text of the message that will be sent to the user should she 193 | # want to reset her password, or change other registration info. 194 | # The lines must be indented, and must begin with '>'. The initial 195 | # spaces and '> ' will be stripped off. $username$ will be replaced 196 | # by the user's username, $useremail$ by her email address, 197 | # $hostname$ by the hostname on which the wiki is running (as 198 | # returned by the hostname system call), $port$ by the port on 199 | # which the wiki is running, and $resetlink$ by the 200 | # relative path of a reset link derived from the user's existing 201 | # hashed password. If your gitit wiki is being proxied to a location 202 | # other than the root path of $port$, you should change the link to 203 | # reflect this: for example, to 204 | # http://$hostname$/path/to/wiki$resetlink$ or 205 | # http://gitit.$hostname$$resetlink$ 206 | 207 | use-feed: no 208 | # specifies whether an ATOM feed should be enabled (for the site and for 209 | # individual pages) 210 | 211 | base-url: 212 | # the base URL of the wiki, to be used in constructing feed IDs. 213 | # If this field is left blank, gitit will get the base URL from the 214 | # request header 'Host'. For most users, this should be fine, but 215 | # if you are proxying a gitit instance to a subdirectory URL, you will 216 | # want to set this manually. 217 | 218 | feed-days: 14 219 | # number of days to be included in feeds. 220 | 221 | feed-refresh-time: 60 222 | # number of minutes to cache feeds before refreshing 223 | 224 | pdf-export: no 225 | # if yes, PDF will appear in export options. PDF will be created using 226 | # pdflatex, which must be installed and in the path. Note that PDF 227 | # exports create significant additional server load. 228 | 229 | pandoc-user-data: 230 | # if a directory is specified, this will be searched for pandoc 231 | # customizations. These can include a templates/ directory for custom 232 | # templates for various export formats, an S5 directory for custom 233 | # S5 styles, and a reference.odt for ODT exports. If no directory is 234 | # specified, $HOME/.pandoc will be searched. See pandoc's README for 235 | # more information. 236 | 237 | -------------------------------------------------------------------------------- /Network/Gitit/Config.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP, FlexibleContexts #-} 2 | {- 3 | Copyright (C) 2009 John MacFarlane 4 | 5 | This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program; if not, write to the Free Software 17 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | -} 19 | 20 | {- | Functions for parsing command line options and reading the config file. 21 | -} 22 | 23 | module Network.Gitit.Config ( getConfigFromFile 24 | , getDefaultConfig 25 | , readMimeTypesFile ) 26 | where 27 | import Network.Gitit.Types 28 | import Network.Gitit.Server (mimeTypes) 29 | import Network.Gitit.Framework 30 | import Network.Gitit.Authentication (formAuthHandlers, httpAuthHandlers) 31 | import Network.Gitit.Util (parsePageType, readFileUTF8) 32 | import System.Log.Logger (logM, Priority(..)) 33 | import qualified Data.Map as M 34 | import Data.ConfigFile hiding (readfile) 35 | import Control.Monad.Error 36 | import System.Log.Logger () 37 | import Data.List (intercalate) 38 | import Data.Char (toLower, toUpper, isDigit) 39 | import Paths_gitit (getDataFileName) 40 | import System.FilePath (()) 41 | import Text.Pandoc hiding (MathML, WebTeX) 42 | 43 | forceEither :: Show e => Either e a -> a 44 | forceEither = either (error . show) id 45 | 46 | -- | Get configuration from config file. 47 | getConfigFromFile :: FilePath -> IO Config 48 | getConfigFromFile fname = do 49 | cp <- getDefaultConfigParser 50 | readfile cp fname >>= extractConfig . forceEither 51 | 52 | -- | A version of readfile that treats the file as UTF-8. 53 | readfile :: MonadError CPError m 54 | => ConfigParser 55 | -> FilePath 56 | -> IO (m ConfigParser) 57 | readfile cp path' = do 58 | contents <- readFileUTF8 path' 59 | return $ readstring cp contents 60 | 61 | extractConfig :: ConfigParser -> IO Config 62 | extractConfig cp = do 63 | config' <- runErrorT $ do 64 | cfRepositoryType <- get cp "DEFAULT" "repository-type" 65 | cfRepositoryPath <- get cp "DEFAULT" "repository-path" 66 | cfDefaultPageType <- get cp "DEFAULT" "default-page-type" 67 | cfMathMethod <- get cp "DEFAULT" "math" 68 | cfShowLHSBirdTracks <- get cp "DEFAULT" "show-lhs-bird-tracks" 69 | cfAuthenticationMethod <- get cp "DEFAULT" "authentication-method" 70 | cfUserFile <- get cp "DEFAULT" "user-file" 71 | cfSessionTimeout <- get cp "DEFAULT" "session-timeout" 72 | cfTemplatesDir <- get cp "DEFAULT" "templates-dir" 73 | cfLogFile <- get cp "DEFAULT" "log-file" 74 | cfLogLevel <- get cp "DEFAULT" "log-level" 75 | cfStaticDir <- get cp "DEFAULT" "static-dir" 76 | cfPlugins <- get cp "DEFAULT" "plugins" 77 | cfTableOfContents <- get cp "DEFAULT" "table-of-contents" 78 | cfMaxUploadSize <- get cp "DEFAULT" "max-upload-size" 79 | cfMaxPageSize <- get cp "DEFAULT" "max-page-size" 80 | cfPort <- get cp "DEFAULT" "port" 81 | cfDebugMode <- get cp "DEFAULT" "debug-mode" 82 | cfFrontPage <- get cp "DEFAULT" "front-page" 83 | cfNoEdit <- get cp "DEFAULT" "no-edit" 84 | cfNoDelete <- get cp "DEFAULT" "no-delete" 85 | cfDefaultSummary <- get cp "DEFAULT" "default-summary" 86 | cfAccessQuestion <- get cp "DEFAULT" "access-question" 87 | cfAccessQuestionAnswers <- get cp "DEFAULT" "access-question-answers" 88 | cfUseRecaptcha <- get cp "DEFAULT" "use-recaptcha" 89 | cfRecaptchaPublicKey <- get cp "DEFAULT" "recaptcha-public-key" 90 | cfRecaptchaPrivateKey <- get cp "DEFAULT" "recaptcha-private-key" 91 | cfCompressResponses <- get cp "DEFAULT" "compress-responses" 92 | cfUseCache <- get cp "DEFAULT" "use-cache" 93 | cfCacheDir <- get cp "DEFAULT" "cache-dir" 94 | cfMimeTypesFile <- get cp "DEFAULT" "mime-types-file" 95 | cfMailCommand <- get cp "DEFAULT" "mail-command" 96 | cfResetPasswordMessage <- get cp "DEFAULT" "reset-password-message" 97 | cfUseFeed <- get cp "DEFAULT" "use-feed" 98 | cfBaseUrl <- get cp "DEFAULT" "base-url" 99 | cfWikiTitle <- get cp "DEFAULT" "wiki-title" 100 | cfFeedDays <- get cp "DEFAULT" "feed-days" 101 | cfFeedRefreshTime <- get cp "DEFAULT" "feed-refresh-time" 102 | cfPDFExport <- get cp "DEFAULT" "pdf-export" 103 | cfPandocUserData <- get cp "DEFAULT" "pandoc-user-data" 104 | let (pt, lhs) = parsePageType cfDefaultPageType 105 | let markupHelpFile = show pt ++ if lhs then "+LHS" else "" 106 | markupHelpPath <- liftIO $ getDataFileName $ "data" "markupHelp" markupHelpFile 107 | markupHelpText <- liftM (writeHtmlString defaultWriterOptions . readMarkdown defaultParserState) $ 108 | liftIO $ readFileUTF8 markupHelpPath 109 | 110 | mimeMap' <- liftIO $ readMimeTypesFile cfMimeTypesFile 111 | let authMethod = map toLower cfAuthenticationMethod 112 | let stripTrailingSlash = reverse . dropWhile (=='/') . reverse 113 | let repotype' = case map toLower cfRepositoryType of 114 | "git" -> Git 115 | "darcs" -> Darcs 116 | "mercurial" -> Mercurial 117 | x -> error $ "Unknown repository type: " ++ x 118 | 119 | return $! Config{ 120 | repositoryPath = cfRepositoryPath 121 | , repositoryType = repotype' 122 | , defaultPageType = pt 123 | , mathMethod = case map toLower cfMathMethod of 124 | "jsmath" -> JsMathScript 125 | "mathml" -> MathML 126 | "google" -> WebTeX "http://chart.apis.google.com/chart?cht=tx&chl=" 127 | _ -> RawTeX 128 | , defaultLHS = lhs 129 | , showLHSBirdTracks = cfShowLHSBirdTracks 130 | , withUser = case authMethod of 131 | "form" -> withUserFromSession 132 | "http" -> withUserFromHTTPAuth 133 | _ -> id 134 | , authHandler = case authMethod of 135 | "form" -> msum formAuthHandlers 136 | "http" -> msum httpAuthHandlers 137 | _ -> mzero 138 | , userFile = cfUserFile 139 | , sessionTimeout = readNumber "session-timeout" cfSessionTimeout * 60 -- convert minutes -> seconds 140 | , templatesDir = cfTemplatesDir 141 | , logFile = cfLogFile 142 | , logLevel = let levelString = map toUpper cfLogLevel 143 | levels = ["DEBUG", "INFO", "NOTICE", "WARNING", "ERROR", 144 | "CRITICAL", "ALERT", "EMERGENCY"] 145 | in if levelString `elem` levels 146 | then read levelString 147 | else error $ "Invalid log-level.\nLegal values are: " ++ intercalate ", " levels 148 | , staticDir = cfStaticDir 149 | , pluginModules = splitCommaList cfPlugins 150 | , tableOfContents = cfTableOfContents 151 | , maxUploadSize = readSize "max-upload-size" cfMaxUploadSize 152 | , maxPageSize = readSize "max-page-size" cfMaxPageSize 153 | , portNumber = readNumber "port" cfPort 154 | , debugMode = cfDebugMode 155 | , frontPage = cfFrontPage 156 | , noEdit = splitCommaList cfNoEdit 157 | , noDelete = splitCommaList cfNoDelete 158 | , defaultSummary = cfDefaultSummary 159 | , accessQuestion = if null cfAccessQuestion 160 | then Nothing 161 | else Just (cfAccessQuestion, splitCommaList cfAccessQuestionAnswers) 162 | , useRecaptcha = cfUseRecaptcha 163 | , recaptchaPublicKey = cfRecaptchaPublicKey 164 | , recaptchaPrivateKey = cfRecaptchaPrivateKey 165 | , compressResponses = cfCompressResponses 166 | , useCache = cfUseCache 167 | , cacheDir = cfCacheDir 168 | , mimeMap = mimeMap' 169 | , mailCommand = cfMailCommand 170 | , resetPasswordMessage = fromQuotedMultiline cfResetPasswordMessage 171 | , markupHelp = markupHelpText 172 | , useFeed = cfUseFeed 173 | , baseUrl = stripTrailingSlash cfBaseUrl 174 | , wikiTitle = cfWikiTitle 175 | , feedDays = readNumber "feed-days" cfFeedDays 176 | , feedRefreshTime = readNumber "feed-refresh-time" cfFeedRefreshTime 177 | , pdfExport = cfPDFExport 178 | , pandocUserData = if null cfPandocUserData 179 | then Nothing 180 | else Just cfPandocUserData } 181 | case config' of 182 | Left (ParseError e, e') -> error $ "Parse error: " ++ e ++ "\n" ++ e' 183 | Left e -> error (show e) 184 | Right c -> return c 185 | 186 | fromQuotedMultiline :: String -> String 187 | fromQuotedMultiline = unlines . map doline . lines . dropWhile (`elem` " \t\n") 188 | where doline = dropWhile (`elem` " \t") . dropGt 189 | dropGt ('>':' ':xs) = xs 190 | dropGt ('>':xs) = xs 191 | dropGt x = x 192 | 193 | readNumber :: (Num a, Read a) => String -> String -> a 194 | readNumber _ x | all isDigit x = read x 195 | readNumber opt _ = error $ opt ++ " must be a number." 196 | 197 | readSize :: (Num a, Read a) => String -> String -> a 198 | readSize opt x = 199 | case reverse x of 200 | ('K':_) -> readNumber opt (init x) * 1000 201 | ('M':_) -> readNumber opt (init x) * 1000000 202 | ('G':_) -> readNumber opt (init x) * 1000000000 203 | _ -> readNumber opt x 204 | 205 | splitCommaList :: String -> [String] 206 | splitCommaList l = 207 | let (first,rest) = break (== ',') l 208 | first' = lrStrip first 209 | in case rest of 210 | [] -> if null first' then [] else [first'] 211 | (_:rs) -> first' : splitCommaList rs 212 | 213 | lrStrip :: String -> String 214 | lrStrip = reverse . dropWhile isWhitespace . reverse . dropWhile isWhitespace 215 | where isWhitespace = (`elem` " \t\n") 216 | 217 | getDefaultConfigParser :: IO ConfigParser 218 | getDefaultConfigParser = do 219 | cp <- getDataFileName "data/default.conf" >>= readfile emptyCP 220 | return $ forceEither cp 221 | 222 | -- | Returns the default gitit configuration. 223 | getDefaultConfig :: IO Config 224 | getDefaultConfig = getDefaultConfigParser >>= extractConfig 225 | 226 | -- | Read a file associating mime types with extensions, and return a 227 | -- map from extensions to types. Each line of the file consists of a 228 | -- mime type, followed by space, followed by a list of zero or more 229 | -- extensions, separated by spaces. Example: text/plain txt text 230 | readMimeTypesFile :: FilePath -> IO (M.Map String String) 231 | readMimeTypesFile f = catch 232 | (liftM (foldr go M.empty . map words . lines) $ readFileUTF8 f) 233 | handleMimeTypesFileNotFound 234 | where go [] m = m -- skip blank lines 235 | go (x:xs) m = foldr (\ext -> M.insert ext x) m xs 236 | handleMimeTypesFileNotFound e = do 237 | logM "gitit" WARNING $ "Could not read mime types file: " ++ 238 | f ++ "\n" ++ show e ++ "\n" ++ "Using defaults instead." 239 | return mimeTypes 240 | 241 | {- 242 | -- | Ready collection of common mime types. (Copied from 243 | -- Happstack.Server.HTTP.FileServe.) 244 | mimeTypes :: M.Map String String 245 | mimeTypes = M.fromList 246 | [("xml","application/xml") 247 | ,("xsl","application/xml") 248 | ,("js","text/javascript") 249 | ,("html","text/html") 250 | ,("htm","text/html") 251 | ,("css","text/css") 252 | ,("gif","image/gif") 253 | ,("jpg","image/jpeg") 254 | ,("png","image/png") 255 | ,("txt","text/plain") 256 | ,("doc","application/msword") 257 | ,("exe","application/octet-stream") 258 | ,("pdf","application/pdf") 259 | ,("zip","application/zip") 260 | ,("gz","application/x-gzip") 261 | ,("ps","application/postscript") 262 | ,("rtf","application/rtf") 263 | ,("wav","application/x-wav") 264 | ,("hs","text/plain")] 265 | -} 266 | -------------------------------------------------------------------------------- /Network/Gitit/Export.hs: -------------------------------------------------------------------------------- 1 | {- 2 | Copyright (C) 2009 John MacFarlane 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program; if not, write to the Free Software 16 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | -} 18 | 19 | {- Functions for exporting wiki pages in various formats. 20 | -} 21 | 22 | module Network.Gitit.Export ( exportFormats ) where 23 | import Text.Pandoc hiding (HTMLMathMethod(..)) 24 | import qualified Text.Pandoc as Pandoc 25 | import Text.Pandoc.S5 (s5HeaderIncludes) 26 | import Text.Pandoc.Shared (escapeStringUsing, readDataFile) 27 | import Network.Gitit.Server 28 | import Network.Gitit.Framework (pathForPage, getWikiBase) 29 | import Network.Gitit.Util (withTempDir, readFileUTF8) 30 | import Network.Gitit.State (getConfig) 31 | import Network.Gitit.Types 32 | import Network.Gitit.Cache (cacheContents, lookupCache) 33 | import Control.Monad.Trans (liftIO) 34 | import Control.Monad (unless, when) 35 | import Text.XHtml (noHtml) 36 | import qualified Data.ByteString as B 37 | import qualified Data.ByteString.Lazy as L 38 | import Data.ByteString.Lazy.UTF8 (fromString) 39 | import System.FilePath ((<.>), ()) 40 | import Control.Exception (throwIO) 41 | import System.Environment (getEnvironment) 42 | import System.Exit (ExitCode(..)) 43 | import System.IO (openTempFile) 44 | import System.Directory (getCurrentDirectory, setCurrentDirectory, removeFile) 45 | import System.Process (runProcess, waitForProcess) 46 | import Codec.Binary.UTF8.String (encodeString) 47 | 48 | defaultRespOptions :: WriterOptions 49 | defaultRespOptions = defaultWriterOptions { writerStandalone = True } 50 | 51 | respond :: String 52 | -> String 53 | -> (Pandoc -> IO L.ByteString) 54 | -> String 55 | -> Pandoc 56 | -> Handler 57 | respond mimetype ext fn page doc = liftIO (fn doc) >>= 58 | ok . setContentType mimetype . 59 | (if null ext then id else setFilename (page ++ "." ++ ext)) . 60 | toResponseBS (B.empty) 61 | 62 | respondX :: String -> String -> String 63 | -> (WriterOptions -> Pandoc -> IO L.ByteString) 64 | -> WriterOptions -> String -> Pandoc -> Handler 65 | respondX templ mimetype ext fn opts page doc = do 66 | cfg <- getConfig 67 | template' <- liftIO $ getDefaultTemplate (pandocUserData cfg) templ 68 | template <- case template' of 69 | Right t -> return t 70 | Left e -> liftIO $ throwIO e 71 | doc' <- if ext `elem` ["odt","pdf","epub"] 72 | then fixURLs doc 73 | else return doc 74 | respond mimetype ext (fn opts{writerTemplate = template 75 | ,writerSourceDirectory = repositoryPath cfg 76 | ,writerUserDataDir = pandocUserData cfg}) 77 | page doc' 78 | 79 | respondS :: String -> String -> String -> (WriterOptions -> Pandoc -> String) 80 | -> WriterOptions -> String -> Pandoc -> Handler 81 | respondS templ mimetype ext fn = 82 | respondX templ mimetype ext (\o d -> return $ fromString $ fn o d) 83 | 84 | respondLaTeX :: String -> Pandoc -> Handler 85 | respondLaTeX = respondS "latex" "application/x-latex" "tex" 86 | writeLaTeX defaultRespOptions 87 | 88 | respondConTeXt :: String -> Pandoc -> Handler 89 | respondConTeXt = respondS "context" "application/x-context" "tex" 90 | writeConTeXt defaultRespOptions 91 | 92 | 93 | respondRTF :: String -> Pandoc -> Handler 94 | respondRTF = respondS "rtf" "application/rtf" "rtf" 95 | writeRTF defaultRespOptions 96 | 97 | respondRST :: String -> Pandoc -> Handler 98 | respondRST = respondS "rst" "text/plain; charset=utf-8" "" 99 | writeRST defaultRespOptions{writerReferenceLinks = True} 100 | 101 | respondMarkdown :: String -> Pandoc -> Handler 102 | respondMarkdown = respondS "markdown" "text/plain; charset=utf-8" "" 103 | writeMarkdown defaultRespOptions{writerReferenceLinks = True} 104 | 105 | respondPlain :: String -> Pandoc -> Handler 106 | respondPlain = respondS "plain" "text/plain; charset=utf-8" "" 107 | writePlain defaultRespOptions 108 | 109 | respondMan :: String -> Pandoc -> Handler 110 | respondMan = respondS "man" "text/plain; charset=utf-8" "" 111 | writeMan defaultRespOptions 112 | 113 | respondS5 :: String -> Pandoc -> Handler 114 | respondS5 pg doc = do 115 | cfg <- getConfig 116 | base' <- getWikiBase 117 | inc <- liftIO $ s5HeaderIncludes (pandocUserData cfg) 118 | let math = case mathMethod cfg of 119 | MathML -> Pandoc.MathML Nothing 120 | WebTeX u -> Pandoc.WebTeX u 121 | JsMathScript -> Pandoc.JsMath 122 | (Just $ base' ++ "/js/jsMath/easy/load.js") 123 | _ -> Pandoc.PlainMath 124 | variables' <- if mathMethod cfg == MathML 125 | then do 126 | s <- liftIO $ readDataFile (pandocUserData cfg) $ 127 | "data""MathMLinHTML.js" 128 | return [("mathml-script", s),("s5includes",inc)] 129 | else return [("s5includes",inc)] 130 | respondS "s5" "text/html; charset=utf-8" "" 131 | writeHtmlString 132 | defaultRespOptions{writerSlideVariant = S5Slides 133 | ,writerIncremental = True 134 | ,writerVariables = variables' 135 | ,writerHTMLMathMethod = math} 136 | pg doc 137 | 138 | respondSlidy :: String -> Pandoc -> Handler 139 | respondSlidy pg doc = do 140 | cfg <- getConfig 141 | base' <- getWikiBase 142 | let math = case mathMethod cfg of 143 | MathML -> Pandoc.MathML Nothing 144 | WebTeX u -> Pandoc.WebTeX u 145 | JsMathScript -> Pandoc.JsMath 146 | (Just $ base' ++ "/js/jsMath/easy/load.js") 147 | _ -> Pandoc.PlainMath 148 | variables' <- if mathMethod cfg == MathML 149 | then do 150 | s <- liftIO $ readDataFile (pandocUserData cfg) $ 151 | "data""MathMLinHTML.js" 152 | return [("mathml-script", s)] 153 | else return [] 154 | respondS "slidy" "text/html; charset=utf-8" "" 155 | writeHtmlString 156 | defaultRespOptions{writerSlideVariant = SlidySlides 157 | ,writerIncremental = True 158 | ,writerHTMLMathMethod = math 159 | ,writerVariables = variables'} 160 | pg doc 161 | 162 | respondTexinfo :: String -> Pandoc -> Handler 163 | respondTexinfo = respondS "texinfo" "application/x-texinfo" "texi" 164 | writeTexinfo defaultRespOptions 165 | 166 | respondDocbook :: String -> Pandoc -> Handler 167 | respondDocbook = respondS "docbook" "application/docbook+xml" "xml" 168 | writeDocbook defaultRespOptions 169 | 170 | respondMediaWiki :: String -> Pandoc -> Handler 171 | respondMediaWiki = respondS "mediawiki" "text/plain; charset=utf-8" "" 172 | writeMediaWiki defaultRespOptions 173 | 174 | respondODT :: String -> Pandoc -> Handler 175 | respondODT = respondX "opendocument" "application/vnd.oasis.opendocument.text" 176 | "odt" (writeODT Nothing) defaultRespOptions 177 | 178 | respondEPUB :: String -> Pandoc -> Handler 179 | respondEPUB = respondX "html" "application/epub+zip" "epub" (writeEPUB Nothing) 180 | defaultRespOptions 181 | 182 | -- | Run shell command and return error status. Assumes 183 | -- UTF-8 locale. Note that this does not actually go through \/bin\/sh! 184 | runShellCommand :: FilePath -- ^ Working directory 185 | -> Maybe [(String, String)] -- ^ Environment 186 | -> String -- ^ Command 187 | -> [String] -- ^ Arguments 188 | -> IO ExitCode 189 | runShellCommand workingDir environment command optionList = do 190 | (errPath, err) <- openTempFile workingDir "err" 191 | hProcess <- runProcess (encodeString command) (map encodeString optionList) 192 | (Just workingDir) environment Nothing (Just err) (Just err) 193 | status <- waitForProcess hProcess 194 | removeFile errPath 195 | return status 196 | 197 | respondPDF :: String -> Pandoc -> Handler 198 | respondPDF page old_pndc = fixURLs old_pndc >>= \pndc -> do 199 | cfg <- getConfig 200 | unless (pdfExport cfg) $ error "PDF export disabled" 201 | let cacheName = pathForPage page ++ ".export.pdf" 202 | cached <- if useCache cfg 203 | then lookupCache cacheName 204 | else return Nothing 205 | pdf' <- case cached of 206 | Just (_modtime, bs) -> return $ Right (False, L.fromChunks [bs]) 207 | Nothing -> liftIO $ withTempDir "gitit-tmp-pdf" $ \tempdir -> do 208 | template' <- liftIO $ getDefaultTemplate (pandocUserData cfg) "latex" 209 | template <- either throwIO return template' 210 | let toc = tableOfContents cfg 211 | let latex = writeLaTeX defaultRespOptions{writerTemplate = template 212 | ,writerTableOfContents = toc} pndc 213 | let tempfile = "export" <.> "tex" 214 | curdir <- getCurrentDirectory 215 | setCurrentDirectory tempdir 216 | writeFile tempfile latex 217 | -- run pdflatex twice to get the references and toc right 218 | let cmd = "pdflatex" 219 | oldEnv <- getEnvironment 220 | let env = Just $ ("TEXINPUTS",".:" ++ 221 | escapeStringUsing [(' ',"\\ "),('"',"\\\"")] 222 | (curdir repositoryPath cfg) ++ ":") : oldEnv 223 | let opts = ["-interaction=batchmode", "-no-shell-escape", tempfile] 224 | _ <- runShellCommand tempdir env cmd opts 225 | canary <- runShellCommand tempdir env cmd opts 226 | setCurrentDirectory curdir -- restore original location 227 | case canary of 228 | ExitSuccess -> do pdfBS <- L.readFile (tempdir "export" <.> "pdf") 229 | return $ Right (useCache cfg, pdfBS) 230 | ExitFailure n -> do l <- readFileUTF8 (tempdir "export" <.> "log") 231 | return $ Left (n, l) 232 | case pdf' of 233 | Left (n,logOutput) -> simpleErrorHandler ("PDF creation failed with code: " ++ 234 | show n ++ "\n" ++ logOutput) 235 | Right (needsCaching, pdfBS) -> do 236 | when needsCaching $ 237 | cacheContents cacheName $ B.concat . L.toChunks $ pdfBS 238 | ok $ setContentType "application/pdf" $ setFilename (page ++ ".pdf") $ 239 | (toResponse noHtml) {rsBody = pdfBS} 240 | 241 | -- | When we create a PDF or ODT from a Gitit page, we need to fix the URLs of any 242 | -- images on the page. Those URLs will often be relative to the staticDir, but the 243 | -- PDF or ODT processor only understands paths relative to the working directory. 244 | -- 245 | -- Because the working directory will not in general be the root of the gitit instance 246 | -- at the time the Pandoc is fed to e.g. pdflatex, this function replaces the URLs of 247 | -- images in the staticDir with their correct absolute file path. 248 | fixURLs :: Pandoc -> GititServerPart Pandoc 249 | fixURLs pndc = do 250 | curdir <- liftIO getCurrentDirectory 251 | cfg <- getConfig 252 | 253 | let go (Image ils (url, title)) = Image ils (fixURL url, title) 254 | go x = x 255 | 256 | fixURL ('/':url) = curdir staticDir cfg url 257 | fixURL url = url 258 | 259 | return $ processWith go pndc 260 | 261 | exportFormats :: Config -> [(String, String -> Pandoc -> Handler)] 262 | exportFormats cfg = if pdfExport cfg 263 | then ("PDF", respondPDF) : rest 264 | else rest 265 | where rest = [ ("LaTeX", respondLaTeX) -- (description, writer) 266 | , ("ConTeXt", respondConTeXt) 267 | , ("Texinfo", respondTexinfo) 268 | , ("reST", respondRST) 269 | , ("markdown", respondMarkdown) 270 | , ("plain text",respondPlain) 271 | , ("MediaWiki", respondMediaWiki) 272 | , ("man", respondMan) 273 | , ("DocBook", respondDocbook) 274 | , ("Slidy", respondSlidy) 275 | , ("S5", respondS5) 276 | , ("ODT", respondODT) 277 | , ("EPUB", respondEPUB) 278 | , ("RTF", respondRTF) ] 279 | -------------------------------------------------------------------------------- /TANGOICONS: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution-ShareAlike 2.5 License Agreement 2 | 3 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. 4 | 5 | License 6 | 7 | THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. 8 | 9 | BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. 10 | 11 | 1. Definitions 12 | 13 | 1. "Collective Work" means a work, such as a periodical issue, anthology or encyclopedia, in which the Work in its entirety in unmodified form, along with a number of other contributions, constituting separate and independent works in themselves, are assembled into a collective whole. A work that constitutes a Collective Work will not be considered a Derivative Work (as defined below) for the purposes of this License. 14 | 2. "Derivative Work" means a work based upon the Work or upon the Work and other pre-existing works, such as a translation, musical arrangement, dramatization, fictionalization, motion picture version, sound recording, art reproduction, abridgment, condensation, or any other form in which the Work may be recast, transformed, or adapted, except that a work that constitutes a Collective Work will not be considered a Derivative Work for the purpose of this License. For the avoidance of doubt, where the Work is a musical composition or sound recording, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered a Derivative Work for the purpose of this License. 15 | 3. "Licensor" means the individual or entity that offers the Work under the terms of this License. 16 | 4. "Original Author" means the individual or entity who created the Work. 17 | 5. "Work" means the copyrightable work of authorship offered under the terms of this License. 18 | 6. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. 19 | 7. "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike. 20 | 21 | 2. Fair Use Rights. Nothing in this license is intended to reduce, limit, or restrict any rights arising from fair use, first sale or other limitations on the exclusive rights of the copyright owner under copyright law or other applicable laws. 22 | 23 | 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: 24 | 25 | 1. to reproduce the Work, to incorporate the Work into one or more Collective Works, and to reproduce the Work as incorporated in the Collective Works; 26 | 2. to create and reproduce Derivative Works; 27 | 3. to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission the Work including as incorporated in Collective Works; 28 | 4. to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission Derivative Works. 29 | 5. 30 | 31 | For the avoidance of doubt, where the work is a musical composition: 32 | 1. Performance Royalties Under Blanket Licenses. Licensor waives the exclusive right to collect, whether individually or via a performance rights society (e.g. ASCAP, BMI, SESAC), royalties for the public performance or public digital performance (e.g. webcast) of the Work. 33 | 2. Mechanical Rights and Statutory Royalties. Licensor waives the exclusive right to collect, whether individually or via a music rights society or designated agent (e.g. Harry Fox Agency), royalties for any phonorecord You create from the Work ("cover version") and distribute, subject to the compulsory license created by 17 USC Section 115 of the US Copyright Act (or the equivalent in other jurisdictions). 34 | 6. Webcasting Rights and Statutory Royalties. For the avoidance of doubt, where the Work is a sound recording, Licensor waives the exclusive right to collect, whether individually or via a performance-rights society (e.g. SoundExchange), royalties for the public digital performance (e.g. webcast) of the Work, subject to the compulsory license created by 17 USC Section 114 of the US Copyright Act (or the equivalent in other jurisdictions). 35 | 36 | The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. All rights not expressly granted by Licensor are hereby reserved. 37 | 38 | 4. Restrictions.The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: 39 | 40 | 1. You may distribute, publicly display, publicly perform, or publicly digitally perform the Work only under the terms of this License, and You must include a copy of, or the Uniform Resource Identifier for, this License with every copy or phonorecord of the Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Work that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute, publicly display, publicly perform, or publicly digitally perform the Work with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License Agreement. The above applies to the Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Work itself to be made subject to the terms of this License. If You create a Collective Work, upon notice from any Licensor You must, to the extent practicable, remove from the Collective Work any credit as required by clause 4(c), as requested. If You create a Derivative Work, upon notice from any Licensor You must, to the extent practicable, remove from the Derivative Work any credit as required by clause 4(c), as requested. 41 | 2. You may distribute, publicly display, publicly perform, or publicly digitally perform a Derivative Work only under the terms of this License, a later version of this License with the same License Elements as this License, or a Creative Commons iCommons license that contains the same License Elements as this License (e.g. Attribution-ShareAlike 2.5 Japan). You must include a copy of, or the Uniform Resource Identifier for, this License or other license specified in the previous sentence with every copy or phonorecord of each Derivative Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Derivative Works that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder, and You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute, publicly display, publicly perform, or publicly digitally perform the Derivative Work with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License Agreement. The above applies to the Derivative Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Derivative Work itself to be made subject to the terms of this License. 42 | 3. If you distribute, publicly display, publicly perform, or publicly digitally perform the Work or any Derivative Works or Collective Works, You must keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or (ii) if the Original Author and/or Licensor designate another party or parties (e.g. a sponsor institute, publishing entity, journal) for attribution in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; the title of the Work if supplied; to the extent reasonably practicable, the Uniform Resource Identifier, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and in the case of a Derivative Work, a credit identifying the use of the Work in the Derivative Work (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). Such credit may be implemented in any reasonable manner; provided, however, that in the case of a Derivative Work or Collective Work, at a minimum such credit will appear where any other comparable authorship credit appears and in a manner at least as prominent as such other comparable authorship credit. 43 | 44 | 5. Representations, Warranties and Disclaimer 45 | 46 | UNLESS OTHERWISE AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE MATERIALS, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 47 | 48 | 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 49 | 50 | 7. Termination 51 | 52 | 1. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Derivative Works or Collective Works from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. 53 | 2. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. 54 | 55 | 8. Miscellaneous 56 | 57 | 1. Each time You distribute or publicly digitally perform the Work or a Collective Work, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. 58 | 2. Each time You distribute or publicly digitally perform a Derivative Work, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. 59 | 3. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. 60 | 4. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. 61 | 5. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. 62 | 63 | Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. 64 | 65 | Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, neither party will use the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. 66 | 67 | Creative Commons may be contacted at http://creativecommons.org/. 68 | --------------------------------------------------------------------------------