wembley (empty) → 0.1.0.0
raw patch · 7 files changed
+364/−0 lines, 7 filesdep +basedep +bytestringdep +filemanipsetup-changed
Dependencies added: base, bytestring, filemanip, filepath, optparse-applicative, split
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- src/Latex.hs +76/−0
- src/Main.hs +65/−0
- src/Markdown.hs +61/−0
- src/Options.hs +97/−0
- wembley.cabal +33/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Daniel Lovasko (c) 2016++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Daniel Lovasko nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ src/Latex.hs view
@@ -0,0 +1,76 @@+{-# LANGUAGE OverloadedStrings #-}++module Latex+( render+) where++import Data.Monoid+import qualified Data.ByteString.Char8 as C+import qualified System.FilePath as F++-- | Translate an extension into a Minted language name. Note that+-- extensions that are not recognized will be rendered without any+-- syntax highlighting.+translateExt :: String -- ^ file extension+ -> C.ByteString -- ^ Minted language+translateExt ext+ | ext == "c" || ext == "h" = "c"+ | ext == "sh" = "bash"+ | ext == "hs" = "haskell"+ | ext == "pl" = "perl"+ | ext == "py" = "python"+ | ext == "rb" = "ruby"+ | ext == "java" = "java"+ | otherwise = "text"++-- | Append newline as the last character of a string if it does not end+-- with one.+ensureNewline :: C.ByteString -- ^ old string+ -> C.ByteString -- ^ new string+ensureNewline str+ | C.null str = str+ | C.last str == '\n' = str+ | otherwise = str <> "\n"++-- | All underscores must be escaped in order to prevent the subscript+-- redenring.+escapeUnderscore :: String -- ^ old string+ -> String -- ^ new string+escapeUnderscore [] = []+escapeUnderscore (x:xs)+ | x == '_' = '\\':x:escapeUnderscore xs+ | otherwise = x:escapeUnderscore xs++-- | Apply decoration to a single file and its contents.+decorateFile :: (String, C.ByteString) -- ^ file name & content+ -> C.ByteString -- ^ decorated file+decorateFile (name, content) = C.unlines+ [ "\\section*{" <> name' <> "}"+ , "\\addcontentsline{toc}{subsection}{" <> name' <> "}"+ , "\\begin{minted}{" <> translateExt (tail $ F.takeExtensions name) <> "}"+ , ensureNewline content <> "\\end{minted}"+ , "\n" ]+ where name' = C.pack $ escapeUnderscore name++-- | Wrap the document in a standard envelope.+envelope :: String -- ^ project name+ -> C.ByteString -- ^ content+ -> C.ByteString -- ^ finished document+envelope name content = C.unlines+ [ "\\documentclass{article}"+ , "\\usepackage{minted}"+ , "\\usepackage{fullpage}"+ , "\\begin{document}"+ , "\\centerline{\\bf{\\Huge{" <> name' <> "}}}"+ , "\\bigskip"+ , "\\tableofcontents"+ , "\\bigskip"+ , content+ , "\\end{document}" ]+ where name' = C.pack $ escapeUnderscore name++-- | Apply decoration to a whole codebase in order to create a document.+render :: String -- ^ project name+ -> [(String, C.ByteString)] -- ^ file names & contents+ -> C.ByteString -- ^ final document+render name entries = envelope name (C.unlines $ map decorateFile entries)
+ src/Main.hs view
@@ -0,0 +1,65 @@+import Data.List+import Data.List.Split (splitOn)+import Options.Applicative (execParser)+import qualified System.FilePath as F+import qualified System.FilePath.Find as F+import qualified Data.ByteString.Char8 as C++import Latex+import Markdown+import Options+++-- | Convert paths to be relative to the project root.+relativePaths :: Options -- ^ command-line options+ -> [FilePath] -- ^ absolute file paths+ -> [FilePath] -- ^ relative file paths+relativePaths options = map (F.makeRelative (getRootDir options))++-- | Generate the contents of the resuling document.+generateOutput :: Options -- ^ command-line options+ -> [(String, C.ByteString)] -- ^ file names & contents+ -> C.ByteString -- ^ final document+generateOutput options entries = run (getOutputFormat options)+ where+ run FmtLatex = Latex.render name entries+ run FmtMarkdown = Markdown.render name entries+ name = getProjectName options++-- | Sort the file names alphabetically and by the number of path+-- components.+sortNames :: [FilePath] -- ^ file names+ -> [FilePath] -- ^ sorted file names+sortNames names = sortBy compSort (sort names)+ where+ compSort a b = compare (compCount a) (compCount b)+ compCount = length . F.splitPath++-- | Filter predicate to determine relevant file extensions.+acceptExtensions :: Options -- ^ command-line options+ -> F.FindClause Bool -- ^ filter predicate+acceptExtensions options+ | null exts = return False+ | otherwise = foldr (F.||?) (return False) clauses+ where+ clauses = map (F.extension F.==?) withDots+ withDots = map ('.' :) $ filter (not . null) (splitOn "," exts)+ exts = getExtensions options++-- | Find all files that are relevant to the provided options.+findFiles :: Options -- ^ options+ -> IO [FilePath] -- ^ file names+findFiles options = do+ let recurse = acceptExtensions options+ files <- F.find F.always recurse (getRootDir options)+ return $ sortNames files++-- | Pretty-printing of a whole codebase into a document.+main :: IO ()+main = do+ options <- execParser Options.parser+ names <- findFiles options+ contents <- mapM C.readFile names+ let entries = zip (relativePaths options names) contents+ C.writeFile (getOutputFile options) (generateOutput options entries)+
+ src/Markdown.hs view
@@ -0,0 +1,61 @@+{-# LANGUAGE OverloadedStrings #-}++module Markdown+( render+) where++import Data.Char+import Data.Monoid+import qualified Data.ByteString.Char8 as C+import qualified System.FilePath as F+++-- | Translate an extension into a GitHub markdown language name. Note+-- that extensions that are not recognized will be rendered without any+-- syntax highlighting.+translateExt :: String -- ^ file extension+ -> C.ByteString -- ^ Markdown language+translateExt ext+ | ext == "c" || ext == "h" = "c"+ | ext == "sh" = "sh"+ | ext == "hs" = "haskell"+ | ext == "pl" = "perl"+ | ext == "py" = "python"+ | ext == "rb" = "ruby"+ | ext == "java" = "java"+ | otherwise = ""++-- | Append newline as the last character of a string if it does not end+-- with one.+ensureNewline :: C.ByteString -- ^ old string+ -> C.ByteString -- ^ new string+ensureNewline str+ | C.null str = str+ | C.last str == '\n' = str+ | otherwise = str <> "\n"++-- | Apply decoration to a single file and its contents.+decorateFile :: (String, C.ByteString) -- ^ file name & content+ -> C.ByteString -- ^ decorated file+decorateFile (name, content) = C.unlines+ [ "## " <> C.pack name+ , "```" <> translateExt (tail $ F.takeExtensions name)+ , ensureNewline content <> "```" ]++-- | Generate the "table of contents" section+generateTOC :: [String] -- ^ file names+ -> C.ByteString -- ^ section content+generateTOC names = C.unlines (header : map (convert . C.pack) names)+ where+ header = "### Files"+ convert name = "* [" <> name <> "](#" <> linkify name <> ")"+ linkify = C.map toLower . C.filter (\c -> isAlpha c || c == '_')++-- | Apply decoration to a whole codebase in order to create a document.+render :: String -- ^ project name+ -> [(String, C.ByteString)] -- ^ file names & contents+ -> C.ByteString -- ^ final document+render name entries = C.unlines+ ["# " <> C.pack name+ , generateTOC $ map fst entries+ , C.unlines $ map decorateFile entries ]
+ src/Options.hs view
@@ -0,0 +1,97 @@+module Options+( Format(..)+, Options(..)+, parser+) where++import Data.Monoid+import Options.Applicative++-- | Output formats.+data Format = FmtLatex -- ^ LaTeX+ | FmtMarkdown -- ^ GitHub Flavoured Markdown++-- | Command-line options.+data Options = Options { getExtensions :: String+ , getProjectName :: String+ , getOutputFormat :: Format+ , getOutputFile :: String+ , getRootDir :: String }++-- | Textual representation of the Format type.+instance Show Format where+ show FmtLatex = "latex"+ show FmtMarkdown = "markdown"++-- | Parse a output format name.+formatReader :: String -- ^ input+ -> Either String Format -- ^ error message | format+formatReader "latex" = Right FmtLatex+formatReader "markdown" = Right FmtMarkdown+formatReader _ = Left "Format not supported"++-- | Relevant file extensions options.+optionExtensions :: Parser String -- ^ parser+optionExtensions = strOption+ $ short 'e'+ <> long "extensions"+ <> value "hs"+ <> metavar "EXTS"+ <> help "Comma-separated list of relevant file extensions"+ <> showDefault++-- | Project name option.+optionProjectName :: Parser String -- ^ parser+optionProjectName = strOption+ $ short 'n'+ <> long "name"+ <> metavar "NAME"+ <> help "Name of the codebase. Appears in footers and as a title"++-- | Output file location option.+optionOutputFile :: Parser String -- ^ parser+optionOutputFile = strOption+ $ short 'o'+ <> long "output"+ <> metavar "PATH"+ <> help "Location of the resulting document"++-- | Output format option.+optionOutputFormat :: Parser Format -- ^ parser+optionOutputFormat = option (eitherReader formatReader)+ $ short 'f'+ <> long "format"+ <> value FmtLatex+ <> metavar "FORMAT"+ <> help "Format of the resulting document, supported: latex, markdown"+ <> showDefault++-- | Root source directory option.+optionRootDir :: Parser String -- ^ parser+optionRootDir = strOption+ $ short 'd'+ <> long "root-dir"+ <> value "."+ <> metavar "PATH"+ <> help "Root source directory path"+ <> showDefault++-- | Command-line user interface.+optionsParser :: Parser Options -- ^ parser+optionsParser = Options+ <$> optionExtensions+ <*> optionProjectName+ <*> optionOutputFormat+ <*> optionOutputFile+ <*> optionRootDir++-- | Description of the utility.+optionsDescription :: InfoMod Options -- ^ parser description+optionsDescription =+ header "wembley - pretty-print a whole codebase into a document"+ <> fullDesc++-- | Parser of the command-line options.+parser :: ParserInfo Options -- ^ parser+parser = info (helper <*> optionsParser) optionsDescription+
+ wembley.cabal view
@@ -0,0 +1,33 @@+name: wembley+version: 0.1.0.0+synopsis: Pretty-printing of codebases+description: Pretty-printing of codebases for the purposes of code+ review on paper or publication of a whole codebase+ in a document form.+homepage: https://github.com/lovasko/wembley+license: BSD3+license-file: LICENSE+author: Daniel Lovasko <daniel.lovasko@gmail.com>+maintainer: Daniel Lovasko <daniel.lovasko@gmail.com>+copyright: 2016 Daniel Lovasko+category: Pretty-printing+build-type: Simple+cabal-version: >=1.10++executable wembley+ hs-source-dirs: src+ main-is: Main.hs+ other-modules: Latex+ , Markdown+ , Options+ build-depends: base >= 4.7 && < 5+ , bytestring+ , filepath+ , filemanip+ , optparse-applicative+ , split+ default-language: Haskell2010++source-repository head+ type: git+ location: https://github.com/lovasko/wembley