packages feed

lentil 0.1.0.2 → 0.1.1.0

raw patch · 31 files changed

+1096/−348 lines, 31 filesdep ~base

Dependency ranges changed: base

Files

+ changes.txt view
@@ -0,0 +1,14 @@+0.1.1.0+-------++- Released Fri 12 Jun 2015 01:38:26 CEST+- make flagword syntax more liberal (-- todo something <-- is valid now)+- more languages available (c++, java, javascript, python, ruby)+- fixed end-of-file bug and double quote string literal++0.1.0.2+-------++- Released Tue 09 Jun 2015 21:05:33 CEST+- Added changes.txt as changeloc+
doc/usr/page.rst view
@@ -3,8 +3,8 @@ author: Francesco Ariis summary: lentil issue tracker user manual tags: lentil,issue,bug,issue tracker,bug tracker,manual-published: 2015-06-15 00:00:00-revised: 2015-06-15 00:00:00+published: 2015-06-09 00:00:00+revised: 2015-06-10 00:00:00 --------------------------------------------------------------------------------  @@ -24,9 +24,11 @@ time and effort and I like the convenience of having bug reports detailed near the offending functions. -``lentil`` helps you with housekeeping, querying, exporting issues without+`lentil`_ helps you with housekeeping, querying, exporting issues without having to modify a single line of code. +.. _`lentil`: http://ariis.it/static/articles/lentil/page.html+ .. raw:: html     <p><br/></p>@@ -34,6 +36,10 @@ Installation ------------ +(if you don't want to compile it yourself, `binaries`_ are available)++.. _`binaries`: http://ariis.it/link/repos/lentil/bin/+ lentil is written in Haskell, which you can easily grab from your OS repository (e.g. for Debian users ``apt-get install haskell-platform``). Once you do that, installing lentil is as easy as:@@ -94,9 +100,25 @@ Input format ------------ -lentil parses issues that start with ``TODO:`` and ``FIXE:``, followed by-a space, followed by freeform text (text can be multiline).+lentil parses basic ``TODO`` issues. The precise syntax is: keyword (one+of ``todo``, ``fixme``, ``xxx``), optional semicolon, followed by+a space, followed by free-form text (text can be multiline). In so many+words, it is quite liberal in what it accepts: +::++  -- These will all be accepted++  // TODO: explanatory issue++  // todo ehy you left an assert out there!++  //Fixme bad coding style++  /* Xxx: add to version control.+          Ask Timothy */++ You can optionally put tags at the end of your issue, like this:  ::@@ -104,11 +126,21 @@   // FIXME: Mr. Burns should enter from the *right* side of the   //        nuclear station [script] [priority:1] -Tags are single words which are useful to catalogue and identify issues.+Tags are single words which are useful to catalogue and identify issues. Use+them aplenty as they will make slicing and dicing your issue-base a breeze. -As now lentil parses haskell source files (``.hs``, ``.lhs``), C source-files (``.c``, ``.h``) and plain text files (``.txt``) and ignores-everything else.+As now lentil parses:++- haskell source files (``.hs``, ``.lhs``)+- C source files (``.c``, ``.h``), C++ (``.cpp``, ``.hpp``), Java (``.java``)+- javascript source files (``.js``)+- python source files (``.py``)+- ruby source files (``.rb``)+- pascal source files (``.pas``, ``.pp``, ``.inc``)+- plain text files (``.txt``)++Every other file-type will get ignored! Contact me if you need another+language to be added.  .. raw:: html 
+ issues.txt view
@@ -0,0 +1,78 @@+doc/dev/issues.txt+     8  from String to Text? [feature:advanced]+    10  colorise output on regexps? [feature:advanced]+    11  allow different markups [feature:advanced]+    12  recognize module names [feature:advanced]+    13  have stuff linked to haskell-platform friendly e non template+        haskell and stuff [feature:intermediate]+    15  html report, come hlint [feature:advanced]+    16  symlinks? [feature:intermediate]+    17  le ricerche è meglio che siano case unsensitive [feature:basic]+    18  check --output pretty, there are trailing white spaces [bug] [u:1]+    19  parser crash on non new line terminated file? [bug] [u:3]+    20  test con 7.6.3 [feature:basic] [test]+    21  todo/fixmes in different sections [request] [u:3]+ +src/Lentil.hs+     1  compile with O2? [refactor]+    12  1st line todo is not recognised [bug]+    21  clean these nested ifs up [lint]+ +src/Lentil/Args.hs+    21  show help option in lentil short help [feature:intermediate]+    23  disambiguation optparse-applicative [feature:intermediate]+    24  help as lentil PATH [ PATH... ] [ OPTIONS ] [feature:intermediate]+    51  uncomment and implement sorting [feature:intermediate]+ +src/Lentil/File.hs+    41  combine funziona su windows? [feature:intermediate]+ +src/Lentil/Parse/Issue.hs+     8  eliminate lookahead? fai il parser davvero da bottom up senza sub+        parsing [lint] [refactor]+    26  streamline parser, indentation doesn't matter now [refactor] [u:3]+    27  last line non ce la fa se non c'è un "\n" [bug] [u:3]+    28  make syntax more flexyble (anche todo babaabab) [bug] [u:2]+    29  bug qk_apostrophe : if holdingshift then paste:='"' else+        paste:=''''; [bug] [request] [u:3]+    31  Text instead of String, for utf8 goodness [bug] [input]+    32  a naked todo should not be recognized an an issue [test]+    33  handle parsing errors gracefully (not it explodes) (vedi+        lentilfail) [bug]+    35  e che fare quando il todo segue un * come nei commenti lunghi c?+        [bug]+    62  add this to (ciString ancillaries?) [refactor]+    71  add blankline to ancillaries? [lint]+   109  lint `incipit`© [lint]+   117  allow tags in before description? [doc] [design]+   142  rimuovi tutti gli eof, oramai non servono [refactor]+   185  partial pattern! [bug] [refactor] [lint]+   189  deduplicate runparsource+ +src/Lentil/Parse/Source.hs+    38  ++ "\n" is inefficient [lint] [refactor]+    56  fallo diventare string così non impazzisci nelle altre parti+    78  lint program parser [lint]+    84  manyTill1 broken? occhio che al primon on chiama ed! [test]+    98  add signatore to sources & tests [lint] [test]+   110  add more langparsers [feature:intermediate]+   112  fallo che sia estensibile e leggibile a compilazione [design] [u:3]+ +src/Lentil/Print.hs+    41  choose report width (test senza tag)+ +src/Lentil/Query.hs+     8  filtering case unsensitive? [test]+ +test/Lentil/ArgsSpec.hs+    10  can't test lOpts, need to make a runner and a validator on the+        output from io [test]+ +test/Lentil/Parse/IssueSpec.hs+    43  eof blankline, ma non c'è sempre un \n in blanklines? [test]+ +test/test-files/lang-comm/blocks.txt+     3  issue one [t] [q:f]+     5  issue two+    11  issue three [a]+    16  issue four
lentil.cabal view
@@ -1,8 +1,9 @@ name:                lentil-version:             0.1.0.2+version:             0.1.1.0 synopsis:            frugal issue tracker-description:         minumum effort, cohesive issue tracker for the rest of us.-                     Check homepage for manual, tutorial.+description:         minumum effort, cohesive issue tracker based on+                     ubiquitous @TODO@s and @FIXME@s conventions.+                     Check homepage for manual, tutorial, examples. homepage:            http://www.ariis.it/static/articles/lentil/page.html license:             GPL-3 license-file:        LICENSE@@ -11,24 +12,37 @@ copyright:           © 2015 Francesco Ariis category:            Development build-type:          Simple-extra-source-files:  README, doc/usr/page.rst, doc/usr/test.zip+tested-with:         GHC==7.10.1+extra-source-files:  test/test-files/lang-comm/clang.c,+                     test/test-files/lang-comm/haskell.hs,+                     test/test-files/lang-comm/javascript.js,+                     test/test-files/lang-comm/out.blocks,+                     test/test-files/lang-comm/pascal.pas,+                     test/test-files/lang-comm/python.py,+                     test/test-files/lang-comm/ruby.rb,+                     test/test-files/lang-comm/test.txt,+                     test/test-files/lang-comm/text.txt,+                     test/test-files/test-proj/fold-a/foo1.hs,+                     test/test-files/test-proj/fold-b/foo2.hs,+                     test/test-files/test-proj/fold-c/foo3.hs,+                     test/test-files/test-proj/fold-c/sub-fold/foo4.hs,+                     test/test-files/test-proj/fold-c/sub-fold/foo5.hs+extra-doc-files:     README, changes.txt, doc/usr/page.rst, doc/usr/test.zip,+                     issues.txt cabal-version:       >=1.10   executable lentil   main-is:             Lentil.hs--  build-depends:       base >=4.8 && <4.9, optparse-applicative >=0.11 && <0.12,+  build-depends:       base >=4.7 && <4.9, optparse-applicative >=0.11 && <0.12,                        regex-tdfa >=1.2 && <1.3, natural-sort >=0.1 && <0.2,                        parsec >=3.1 && <3.2, filepath >=1.3 && <1.5,                        directory >=1.2 && <1.3, filemanip >=0.3 && <0.4,                        ansi-wl-pprint >= 0.6 && < 0.7, csv >= 0.1 && < 0.2-   other-modules:       Lentil.Types, Lentil.Args, Lentil.File, Lentil.Print,-                       Lentil.Query, Lentil.Export, Lentil.Parse-+                       Lentil.Query, Lentil.Export, Lentil.Parse.Source,+                       Lentil.Parse.Issue   hs-source-dirs:      src-   default-language:    Haskell2010  test-suite test@@ -42,6 +56,12 @@                        directory >=1.2 && <1.3, filemanip >=0.3 && <0.4,                        ansi-wl-pprint >= 0.6 && < 0.7, hspec >=1.8,                        csv >= 0.1 && < 0.2+  other-modules:       Lentil.Types, Lentil.Args, Lentil.File, Lentil.Print,+                       Lentil.Query, Lentil.Export, Lentil.Parse.Source,+                       Lentil.Parse.Issue,+                       Lentil.ArgsSpec, Lentil.ExportSpec, Lentil.FileSpec,+                       Lentil.PrintSpec, Lentil.Parse.SourceSpec,+                       Lentil.Parse.IssueSpec, Lentil.QuerySpec   type:                exitcode-stdio-1.0  source-repository head
src/Lentil.hs view
@@ -8,8 +8,9 @@ import Options.Applicative  import qualified Data.Maybe as M+import Data.Monoid --- TODO: Lentil.Parse.Lentil.Parse.Source.manyTill1 (too many) [bug]+-- TODO: compile with O2? [refactor]  main :: IO () main = execParser opts >>= runLentil@@ -27,7 +28,7 @@                                    short 'v'      <>                                    help "show version and copyright info" )     where-          versionCopy = "lentil - a frugal issue tracker, version 1.0.0\n\+          versionCopy = "lentil - a frugal issue tracker, version 0.1.1.0\n\                         \(C) 2015 Francesco Ariis - http://www.ariis.it\n\                         \released under the GNU General Public License 3" 
src/Lentil/File.hs view
@@ -11,7 +11,7 @@ module Lentil.File where  import Lentil.Types-import Lentil.Parse+import Lentil.Parse.Issue  import System.FilePath import System.FilePath.Find
− src/Lentil/Parse.hs
@@ -1,324 +0,0 @@--------------------------------------------------------------------------------- |--- Module      :  Lentil.Parse.Source--- Copyright   :  © 2015 Francesco Ariis--- License     :  GPLv3 (see the LICENSE file)------ Issues parsing from source.--------------------------------------------------------------------------------module Lentil.Parse where--import Lentil.Types--import Text.Parsec--import Control.Applicative hiding ( (<|>), optional, many )--import qualified Data.Char as C-import qualified Data.List as L-import qualified System.FilePath as SF-import qualified System.Directory as D-import qualified System.IO as I-import qualified Data.Either as E----- TODO: eliminate lookahead? fai il parser davvero da bottom up senza sub---       parsing [lint] [refactor] ?----------------- TYPES ----------------type ParSource = Parsec String ParState--data ParState = ParState { psPath :: FilePath }--------------------- PRIMITIVE ---------------------- a unix file is defined as many lines (each one ended by '\n')--- this parser captures the definition and can be used as eof replacement too-eoft :: ParSource ()-eoft = optional (char '\n') *> eof---- i.e. remove unneeded whitespace-htmlify :: String -> String-htmlify cs = unwords . words $ cs----- occhio che il primo carattere non è controllato da ED--- TODO: manyTill1 broken? occhio che al primon on chiama ed! [test]-manyTill1 :: ParSource a -> ParSource b -> ParSource [a]-manyTill1 p ed = (:) <$> p <*> manyTill p ed---------------- TAGS ----------------- simple tags parsing---- tag only-tag :: ParSource Tag-tag = Tag <$> (openPar *> tagLabel <* closePar) <?> "tag"---- anything goes, apart from ' '-tagLabel :: ParSource String-tagLabel = many1 (satisfy sf) <?> "tag label"-    where sf :: Char -> Bool-          sf c | c == closeDel = False-               | C.isSpace c   = False-               | otherwise     = True--openPar, closePar :: ParSource Char-openPar  = char openDel  <?> "open-tag delimiter"-closePar = char closeDel <?> "close-tag delimiter"------------------ ISSUES ------------------- simple issue parsing---- parses a number of issues from a given line-of-text-issues :: ParSource [Issue]-issues = many (try $ manyTill anyChar (lookAhead $ try incipit) *> issue)-         <?> "issues"---- an issue is a flagword, followed by : , followed by some description and--- ended optionally by some tags (No t/f? End by whiteline or eof or--- another TODO).-issue :: ParSource Issue-issue = Issue <$> fmap psPath getState-              <*> fmap sourceLine getPosition-              <*> (incipit *> fmap htmlify freeText)-              <*> (optional spaces *> option [] (try tags))-        <?> "issue"----- flagwords + ": "-incipit :: ParSource ()-incipit = () <$ (choice (map string flagWords) *> string ": ") <?> "incipit"--flagWords :: [String]-flagWords = ["TODO", "FIXME", "NOTE", "XXX"]--tags :: ParSource [Tag]-tags = sepEndBy1 tag spaces <?> "tags"---- any text. Since tags/fields at the end of the issue are optional, we need--- a way to delimit this. Delimiters are: eof, tags/fields, new issue-freeText :: ParSource Description-freeText = manyTill anyChar end <?> "free text"-    where vp p      = try . parsecMap (const ()) $ p-          end       = lookAhead $ choice [vp (spaces *> tag),-                                          vp (spaces *> incipit), vp eoft]------------------ BLOCKS ------------------- blocks parsing, check `block` comment for more info--blocks :: ParSource [Issue]-blocks = concat <$> many1 block---- a block is some text separated by emptyline(s) (a paragraph). Once you--- parse it you need to unwrap it and scan for comments. Whitespace--- is treated like in html.-block :: ParSource [Issue]-block = getPosition                                  >>= \lep ->-        manyTill1 anyChar (try $ emptyline <|> eoft) >>= \rb ->-        getState                                     >>= \ps ->-        case runParser (bi lep) ps "<block-parse>" rb of-            Right r  -> return r-            Left  l  -> parserFail ("block subp: " ++ show l)-        <?> "block"-    where-          bi p = setPosition p >> issues---emptyline :: ParSource ()-emptyline = () <$ (endOfLine *> many (satisfy ws) *> endOfLine) <?> "emptyline"-    where ws c = C.isSpace c && notElem c "\n\r\f\v"-               -- as per definition of isSpace in Data.Char-------------------- COMMENTS --------------------- TYPES ------ StringPos is the comment itself + its indentation level. Conventionally--- multiline comments will have IL of 0 to sever them from single line--- comments-type StringPos  = (Int, Int, String) -- Col, Row, String----- FUNCTIONS ------ Comment is the same, with just line indication, for comments that go--- together indentation wise. Check the utility function sps2cr-type CommentRow = (Int, String) -- row, String--sps2cr :: [[StringPos]] -> [CommentRow]-sps2cr sps = map sp2cr sps-    where sp2r (_,r,_) = r-          sp2s (_,_,s) = s--          sp2cr [] = error "empty group in sps2cr"-          sp2cr zs = (sp2r . head $ zs, myUnlines . map sp2s $ zs)--          -- do not add unneeded \n at end-          myUnlines zs = L.intercalate "\n" zs----- comments: from a source file to something parsable by `blocks`--- indent level 0, to recognise them from single line-multiLine :: String -> String -> ParSource StringPos-multiLine st ed = getPosition >>= \a ->-                  (,,) <$> pure 0-                       <*> pure (sourceLine a)-                       <*> (string st *> manyTill anyChar (try $ string ed))-                  <?> "multiline comment"---- returns single line comment and starting column of such comment-singleLine :: String -> ParSource StringPos -- int: starting col-singleLine st = getPosition >>= \a ->-                (string st *> manyTill anyChar (() <$ endOfLine <|> eoft)) >>= \s ->-                return (sourceColumn a, sourceLine a, s)-                <?> "singleline comment"---rawComment :: String -> String -> String -> ParSource StringPos-rawComment st ed slt = try (multiLine st ed) <|> singleLine slt-                       <?> "raw comment"--rawComments :: String -> String -> String ->-               ParSource [StringPos]-rawComments st ed slt =-        E.rights <$> many1 ( try (Right <$> rawComment st ed slt) <|>-                             try (Left  <$> (() <$ codeString))   <|>-                                 (Left  <$> other ) )-                -- TODO: o many? [test]-        <?> "raw comments"-    where-          initP = lookAhead (try (() <$ string st))  <|> -- long comment-                  lookAhead (try (() <$ string slt)) <|> -- line comment-                  lookAhead (() <$ char '"')         <|> -- code string-                  (() <$ char '\n') -- TODO: use eoft [bug]--          -- non comment stuff-          other = () <$ manyTill anyChar initP -- XXX---- quoted strings, by mauke^-codeString :: ParSource String-codeString = q *> many ((char '\\' *> anyChar) <|> noneOf "\"\\") <* q-             <?> "codestring"-    where q = char '"'---- single lineblock (which will be parsed) plus its original row position-groupStringPos :: [StringPos] -> [CommentRow]-groupStringPos sps =-            let grp    = L.groupBy sameBlock sps'-                -- sps'': now that they are grouped, go back to sp tuple-                c2s qs = map (\(c,r,s,_) -> (c,r,s)) qs-                sps''  = map c2s grp  -- this is a list of lists, hence-                                      -- the doublemap-            in sps2cr sps''-    where -- how to verify if lines are sequential?-          -- subtract [1..] to line-number and you can use Eq on this!--          -- clever hack, the subtraction is the 4th value of the tuple-          sps' = map (\((c,r,s),k) -> (c,r,s,r-k)) (zip sps [1..])--          -- equality function:-          -- multiline blocks are always to be treated alone-          -- single line blocks are to be grouped only if on same col and-          -- sequential lines-          sameBlock (0,_,_,_ ) _            = False-          sameBlock (c,_,_,il) (c',_,_,il') = c == c' && il == il'------------------ SOURCE -----------------source :: String -> String -> String -> ParSource [Issue]-source st ed slt = fmap groupStringPos (rawComments st ed slt) >>= \cc ->-                   fmap psPath getState                        >>= \fp ->-                   fmap concat (mapM (parseCr fp) cc)-    where-          parseCr :: FilePath -> CommentRow -> ParSource [Issue]-          parseCr fp (r,s) = case runParSource (bpp r) fp s of-                               Right y -> pure y-                               Left  _ -> parserFail "<source>"-                                     -- TODO: more meaningful err mes. [duct]-          bpp r            = getPosition                     >>= \p ->-                             setPosition (setSourceLine p r) >>-                             blocks-          -- TODO: elimina runParser per runParSource [duct]---------------- FILE ----------------- main function with wich to parse issues; errors on stderr-issueFinder :: [FilePath] -> IO [Issue]-issueFinder fps = concat <$> mapM (uncurry fileParser) (matchPar fps)-    where -- matches each file w/ its parser, filters parserless files-          matchPar :: [FilePath] -> [(FilePath, ParSource [Issue])]-          matchPar fpz = filtNotPar . map (\f -> (f, langParser f)) $ fpz--          -- I can't use == on ParSource-          filtNotPar :: [(FilePath, Maybe (ParSource [Issue]))] ->-                        [(FilePath, ParSource [Issue])]-          filtNotPar []                 = []-          filtNotPar ((f, Just p):tps)  = (f,p) : filtNotPar tps-          filtNotPar ((_, Nothing):tps) = filtNotPar tps---fileParser :: FilePath -> ParSource [Issue] -> IO [Issue]-fileParser fp p = rfe fp >>= \cs ->-                  case runParSource p fp cs of-                      Left l  -> perr (fp ++ " : parse error " ++ show l) >>-                                 return []-                      Right r -> return r-    where rfe :: FilePath -> IO String-          rfe _ = D.doesFileExist fp >>= \fb ->-                  if fb then readFile fp-                        else perr (fp ++ " : no such file") >> return ""--          perr cs = I.hPutStrLn I.stderr cs--runParSource :: ParSource a -> FilePath -> String -> Either ParseError a-runParSource p fp cs = runParser p (ParState fp) fp cs---- TODO: add more langparsers [feature:intermediate]-langParser :: FilePath -> Maybe (ParSource [Issue])-langParser fp | ext `elem` [".hs", ".lhs"]  = Just haskell-              | ext `elem` [".c", ".h"]     = Just c-              | ext `elem` [".txt"]        = Just blocks-              | otherwise                  = Nothing-    where ext = SF.takeExtension fp--          haskell = source "{-" "-}" "--"-          c       = source "/*" "*/" "//"-            -- Shell: # (Perl: same)-            -- php: as C + Shell-            -- ruby: shell and =begin =end-            -- javascript: C + html-            -- python: # and """-            -- java, objective-c, etc. watch tiobe-            -- html--         -- TODO: do not add more languages but parse every text-         --       file? [design] [u:3]
+ src/Lentil/Parse/Issue.hs view
@@ -0,0 +1,183 @@+-----------------------------------------------------------------------------+-- |+-- Module      :  Lentil.Parse.Issue+-- Copyright   :  © 2015 Francesco Ariis+-- License     :  GPLv3 (see the LICENSE file)+--+-- Issues parsing from comments+-----------------------------------------------------------------------------++module Lentil.Parse.Issue where++import Lentil.Types+import Lentil.Parse.Source (commentParser)++import Text.Parsec+import qualified Text.Parsec.Pos as P++import Control.Applicative hiding ( (<|>), optional, many )++import qualified Data.Char as C+import qualified Data.List as L+import qualified Data.Either as E+import qualified System.IO as I+++-- TODO: eliminate lookahead? fai il parser davvero da bottom up senza sub+--       parsing [lint] [refactor] ?+-- TODO: e che fare quando il todo segue un * come nei commenti lunghi c? [design]+++-----------+-- TYPES --+-----------++type ParIssue a = Parsec String ParState a++-- file we are parsing+data ParState = ParState { psPath    :: FilePath }++++---------------+-- PRIMITIVE --+---------------++-- a unix file is defined as many lines (each one ended by '\n')+-- this parser captures the definition and can be used as eof replacement too+eoft :: ParIssue ()+eoft = optional (char '\n') *> eof+++---------------+-- PRIMITIVE --+---------------++-- TODO: add this to (ciString ancillaries?) [refactor]+-- case insensitive string, lifted from Text.ParserCombinators.Parsec.Rfc2234+ciString :: String -> ParIssue String+ciString s = mapM ciChar s <?> "case insensitive string"+    where ciChar :: Char -> ParIssue Char+          ciChar c = char (C.toLower c) <|> char (C.toUpper c)++-- i.e. remove unneeded whitespace+htmlify :: String -> String+htmlify cs = unwords . words $ cs++-- todo add blankline to ancillaries? [lint]+-- a blank line of text (even at eof)+blankline :: ParIssue ()+blankline = char '\n' *> (() <$ char '\n' <|> eof)+-- blankline = () <$ string "\n\n"+++----------+-- TAGS --+----------++-- simple tags parsing++-- tag only+tag :: ParIssue Tag+tag = Tag <$> (openPar *> tagLabel <* closePar) <?> "tag"++-- anything goes, apart from ' '+tagLabel :: ParIssue String+tagLabel = many1 (satisfy sf) <?> "tag label"+    where sf :: Char -> Bool+          sf c | c == closeDel = False+               | C.isSpace c   = False+               | otherwise     = True++openPar, closePar :: ParIssue Char+openPar  = char openDel  <?> "open-tag delimiter"+closePar = char closeDel <?> "close-tag delimiter"+++-------------+-- INCIPIT --+-------------++-- optional ws + flagwords (case unsensitive) + optional ':' ++ optional1 ws+incipit :: ParIssue ()+incipit = () <$ ( char '\n'                       *>+                  spaces                          *>+                  choice (map ciString flagWords) *>+                  optional (char ':')             *>+                  many1 (char ' ')                   )+          <?> "incipit"++-- TODO: allow tags in before description? [doc] [design]+flagWords :: [String]+flagWords = ["TODO", "FIXME", "XXX"]+++------------+-- ISSUES --+------------++-- simple issue parsing++-- an issue is a flagword, followed by : , followed by some description and+-- ended optionally by some tags (No t/f? End by whiteline or eof or+-- another TODO).+issue :: ParIssue Issue+issue = (Issue <$> fmap psPath getState+               <*> (incipit *> fmap sourceLine getPosition)+               <*> freeText+               <*> option [] (try tags))+        <?> "issue"+++-- any text. Since tags/fields at the end of the issue are optional, we need+-- a way to delimit this. Delimiters are: eof, tags/fields, blank line+freeText :: ParIssue Description+freeText = fmap htmlify (manyTill anyChar end) <?> "free text"+    where vp p      = try . parsecMap (const ()) $ p+          end       = lookAhead $ choice [vp (spaces *> tag),+                                          vp blankline,+                                          vp incipit]++    -- todo rimuovi tutti gli eof, oramai non servono [refactor]++tags :: ParIssue [Tag]+tags = many1 (try $ spaces *> tag) <?> "tags"+++++-- parses a number of issues from a given line-of-text+issues :: ParIssue [Issue]+issues = getPosition >>= \p -> -- horrible hack, search for [duct] for expl-+         setPosition (setSourceLine p (0)) >> -- anation+         many (try $ manyTill anyChar (lookAhead $ try incipit) *> issue)+         <?> "issues"+++----------+-- FILE --+----------++-- main function with wich to parse issues; errors on stderr+issueFinder :: [FilePath] -> IO [Issue]+issueFinder fps = mapM commentParser fps >>= \content ->++                  -- todo partial pattern! [refactor] [lint]+                  let ass = zip fps content+                      con' = map (\(a, Just b) -> (a,b)) .+                             filter ((/= Nothing) . snd) $ ass in+                  mapM pf con' >>= \iss ->++                  return (concat iss)+    where+          pf (fp,cs) = case runParSource issues fp cs of+                         Left l  -> perr (fp ++ " : issue parsing error " +++                                          show l) >> return []+                         Right r -> return r++          perr cs = I.hPutStrLn I.stderr cs++-- todo deduplicate runparsource [lint] [refactor]+runParSource :: ParIssue a -> FilePath -> String -> Either ParseError a+runParSource p fp cs = runParser p (ParState fp) fp cs+
+ src/Lentil/Parse/Source.hs view
@@ -0,0 +1,178 @@+-----------------------------------------------------------------------------+-- |+-- Module      :  Lentil.Parse.Source+-- Copyright   :  © 2015 Francesco Ariis+-- License     :  GPLv3 (see the LICENSE file)+--+-- Comments from source files+-----------------------------------------------------------------------------+++module Lentil.Parse.Source where++import Text.Parsec hiding (Line)++import qualified Data.Char as C+import qualified System.Directory as D+import qualified System.IO as I+import qualified System.FilePath as SF++import Control.Applicative hiding (many, (<|>))++-----------+-- TYPES --+-----------++-- our parsers goal is simple: from a source file, only the comments and+-- newlines+type ParSource a = Parsec String () a++data ParSyntax = ParSyntax { psLineComms  :: [String],+                             psBlockComms :: [(String, String)],+                             psStringLits :: [Char],+                             psCharLits   :: [Char] }+                 deriving (Show)+++------------+-- BLOCKS --+------------++-- TODO: ++ "\n" is inefficient [lint] [refactor]+lineComment :: String -> ParSource String+lineComment s = string s *> fmap (++ "\n") (manyTill anyChar newline)+                <?> "line comment"++blockComment :: (String, String) -> ParSource String+blockComment (i, e) = string i *> manyTill anyChar (try $ string e)+                      <?> "block comment"++-- quoted strings, escaped by \, by mauke^+litString :: Char -> ParSource String+litString ic = q *> many ((char '\\' *> anyChar) <|> noneOf no) <* q+               <?> "codestring"+    where q  = char ic+          no = C.showLitChar ic "\\"++-- quoted single character+litChar :: Char -> ParSource Char+litChar ic = q *> ((char '\\' *> anyChar) <|> anyChar) <* q+             <?> "char string sign"+    where q = char ic+    -- TODO: fallo diventare string così non impazzisci nelle altre parti+    --       [refactor]++-- a program is instructions to the computer. Ends when you meet+-- a well formed element from above (linecomm, blockcom, stringlit,+-- charLit)+program :: ParSyntax -> ParSource String+program ps = manyTill1 anyChar (endp <|> ("" <$ eof))+             <?> "program"+    where+          -- endp :: [ParSource String]+          endp  = lookAhead $ choice $ map try $ map string posts ++ [lchars]++          -- every unambiguous symbol (init comments, listring char)+          posts  = psLineComms ps ++ map fst (psBlockComms ps) +++                   (map (:[]) $ psStringLits ps)++          -- ambiguous symbols (pall'aaa'foo is valid haskell identifier,+          -- so it should stay here+          lchars = "" <$ choice (map litChar (psCharLits ps))++          -- TODO: lint program parser [lint]+++-- occhio che il primo carattere non è controllato da ED+-- TODO: manyTill1 broken? occhio che al primon on chiama ed! [test]+manyTill1 :: ParSource a -> ParSource b -> ParSource [a]+manyTill1 p ed = (:) <$> p <*> manyTill p ed+++------------+-- SOURCE --+------------++-- given a set of lineparsers / blockparsers+source :: ParSyntax -> ParSource String+source ps@(ParSyntax lc bc sl cl) =+                choice (map (try . lineComment) lc) -- line comment+            <|> choice (map (try . blockComment) bc) -- block comment+            <|> (choice (map (onlynl . try . litString) sl))+            <|> ("" <$ (choice (map (try . litChar) cl))) -- todo ugly parens [lint] [refactor]+            <|> (onlynl $ program ps)+            <?> "source file"+    where onlynl a = fmap (filter (== '\n')) a++sources :: ParSyntax -> ParSource String+sources ps = fmap concat $ many1 (source ps)+++--------------+-- SYNTAXES --+--------------++-- TODO: add more langparsers [feature:intermediate]+-- TODO: fallo che sia estensibile e leggibile+--       a compilazione [design] [u:3]++langParser :: String -> Maybe (ParSource String)+langParser fp | ext `elem` [".hs", ".lhs"]         = Just haskell+              | ext `elem` [".c", ".h"]            = Just c+              | ext `elem` [".cpp", ".hpp"]        = Just c -- C+++              | ext `elem` [".java"]               = Just c -- Java+              | ext `elem` [".js"]                 = Just javascript+              | ext `elem` [".py"]                 = Just python+              | ext `elem` [".rb"]                 = Just ruby+              | ext `elem` [".pas", ".pp", ".inc"] = Just pascal+              | ext `elem` [".txt"]                = Just text+              | otherwise                          = Nothing+    where ext = SF.takeExtension fp++haskell, c, pascal, text :: ParSource String+haskell    = sources $ ParSyntax ["--"] [("{-", "-}")] ['"']  ['\'']+c          = sources $ ParSyntax ["//"] [("/*", "*/")] ['"']  ['\'']+javascript = sources $ ParSyntax ["//"] [("/*", "*/")] ['"', '\''] []+pascal     = sources $ ParSyntax ["//"] [("{",  "}" ),+                                         ("(*", "*)")] ['\''] []+python     = sources $ ParSyntax ["#"]  [("\"\"\"",+                                          "\"\"\"")]   ['"', '\''] []+ruby       = sources $ ParSyntax ["#"] [("=begin",+                                         "=end")]      ['"', '\''] []+text       = many anyChar+++-------------+-- PARSERS --+-------------+++commentParser :: FilePath -> IO (Maybe String)+commentParser fp = case langParser fp of+                     Nothing -> return Nothing+                     Just p  -> fileParser fp p >>=+                                return . Just . ('\n':)+                                -- TODO: ugly hack to allow tODOs to be+                                -- parseable even in first line. This is+                                -- coupled with another hack in parser+                                -- `issue` (another module). [refactor]+                                -- [lint] [duct]+++-- errors to stderr+fileParser :: FilePath -> ParSource String -> IO String+fileParser fp p = rfe fp >>= \cs ->+                  case runParSource p fp cs of+                      Left l  -> perr (fp ++ " : parse error " ++ show l) >>+                                 return []+                      Right r -> return r+    where rfe :: FilePath -> IO String+          rfe _ = D.doesFileExist fp >>= \fb ->+                  if fb then readFile fp+                        else perr (fp ++ " : no such file") >> return ""++          perr cs = I.hPutStrLn I.stderr cs++runParSource :: ParSource a -> FilePath -> String -> Either ParseError a+runParSource p fp cs = runParser p () fp cs+
src/Lentil/Print.hs view
@@ -45,7 +45,7 @@ ppTag c t = col (char openDel) <> string (tagString t) <>             col (char closeDel)     where col | c == Red  = red-              | c == Blue = blue+              | otherwise = blue  ppIssue :: Int -> Issue -> Doc ppIssue ind is = indent spInd ( fillSep [string iNum, string "",@@ -63,7 +63,6 @@ ppFile ind is = text (drop 2 $ iFile (head is)) PP.<$>                 vsep (map (ppIssue ind) is) --- TODO: choose report width (test senza tag) ppIssues :: Bool -> [Issue] -> String ppIssues col is = myRender col $ vsep (L.intersperse softline igr)     where
+ test/Lentil/ArgsSpec.hs view
@@ -0,0 +1,40 @@+module Lentil.ArgsSpec where++import Test.Hspec++import Lentil.Types+import Lentil.Query+import Lentil.Args+++-- TEST VARS --++-- TODO: can't test lOpts, need to make a runner and a validator on+--       the output from io [test]++testOpt :: LOptions+testOpt = LOptions (["alpha"], ["beta"]) Csv+                   [filterFilepath "al", filterTagless]+                   [] (Just "foo.txt")++ins :: [String]+ins  = words "alpha --format csv -x beta -p al -t ^ --output foo.txt"++-- out :: Maybe LOptions+-- out = getParseResult .+      -- execParserPure M.mempty (info lOpts (fullDesc)) $ ins+++-- TESTING --++main :: IO ()+main = hspec spec++spec :: Spec+spec = do++  describe "lOpts" $ do+    it "parses options" $+      ins `shouldBe` ins+      --error "args" --out `shouldBe` Just testOpt+
+ test/Lentil/ExportSpec.hs view
@@ -0,0 +1,29 @@+module Lentil.ExportSpec where+++import Test.Hspec++import Lentil.Types+import Lentil.Export+++is :: [Issue]+is = [Issue "file" 1 "da" [Tag "a", Tag "c"],+      Issue "file" 2 "db" [Tag "e:f"]]++csv :: String+csv = "\"Filepath\",\"Row\",\"Description\",\"Tags\"\n\+      \\"file\",\"1\",\"da\",\"a c\"\n\+      \\"file\",\"2\",\"db\",\"e:f\""+++main :: IO ()+main = hspec spec++spec :: Spec+spec = do++  describe "issues2CSV" $ do+    it "exports issues to CSV" $+      issues2CSV is `shouldBe` csv+
+ test/Lentil/FileSpec.hs view
@@ -0,0 +1,46 @@+module Lentil.FileSpec where++import Test.Hspec++import Lentil.File++-- File tests++main :: IO ()+main = hspec spec++bas :: String+bas = "test/test-files/test-proj/"++spec :: Spec+spec = do++  describe "findIssues" $ do+    it "scans directories to find issues" $+      findIssues [bas ++ ""] [] >>= \fiss ->+      length fiss `shouldBe` 5+    it "allows to add specific folders" $+      findIssues [bas ++ ""] [] >>= \fiss ->+      findIssues [bas ++ "fold-a/", bas ++ "fold-b",+                  bas ++ "fold-c"] [] >>= \fiss2 ->+      length fiss `shouldBe` length fiss2+    it "accepts filenames instead of dirs, too" $+      findIssues [bas ++ "fold-a/foo1.hs"] [] >>= \fiss ->+      length fiss `shouldBe` 1+    it "doesn't duplicate issues" $+      findIssues [bas ++ "", bas ++ ""] [] >>= \fiss ->+      length fiss `shouldBe` 5+    it "excludes some folders" $+      findIssues [bas ++ ""] [bas ++ "fold-c/"] >>= \fiss ->+      length fiss `shouldBe` 2+    it "excludes files" $+      findIssues [bas ++ ""] [bas ++ "fold-a/foo1.hs"] >>= \fiss ->+      length fiss `shouldBe` 4+    it "excludes subfolders" $+      findIssues [bas ++ "fold-c/"]+                 [bas ++ "fold-c/sub-fold/"] >>= \fiss ->+      length fiss `shouldBe` 1+    it "doesn't crash on wrong file" $+      findIssues [bas ++ "zxczd"] [] >>= \fiss ->+      length fiss `shouldBe` 0+
+ test/Lentil/Parse/IssueSpec.hs view
@@ -0,0 +1,137 @@+module Lentil.Parse.IssueSpec where++import Test.Hspec+import Text.Parsec ( runParser )+import Text.Parsec.Char+import Text.Parsec.Combinator+import Control.Applicative++import Lentil.Types+import Lentil.Parse.Issue++-- Parsing tests++-- simple parser (choosable if we are at begin of line or else)+sp :: ParIssue a -> String -> Maybe a+sp p cs = either (const Nothing) Just+                    (runParser p (ParState fp) fp cs)+    where fp = "<f>"+++main :: IO ()+main = hspec spec++++spec :: Spec+spec = do++  describe "ciString" $ do+    it "parses a case insensitive string" $+      sp (ciString "tEst") "Test" `shouldBe` Just "Test"++  describe "eoft" $ do+    it "parses a textfile end of file" $+      sp (string "test" <* eoft) "test\n" `shouldBe` Just "test"+    it "or an eof without '\\n'" $+      sp (string "test" <* eoft) "test"   `shouldBe` Just "test"++  describe "blankline" $ do+    it "parses an empty line" $+      sp blankline "\n\nlol"   `shouldBe` Just ()+    it "deals with eof blanklines too" $+      sp blankline "\n" `shouldBe` Just ()++  describe "htmlify" $ do+    it "removes unneeded whitespace" $+      htmlify "one  two " `shouldBe` "one two"+    it "removes newlines too" $+      htmlify "one  two\nthree " `shouldBe` "one two three"++  describe "tag" $ do+    it "parses a tag" $+      fmap tagString (sp tag "[test-tag]") `shouldBe` Just "test-tag"+    it "should not allow whitespace inside a tag" $+      fmap tagString (sp tag "[broken tag]") `shouldBe` Nothing++  describe "incipit" $ do+    it "parses the initial section of an issue, returns nothing" $+       do sp incipit "\nTODO: "  `shouldBe` Just ()+          sp incipit "\nFIXME: "  `shouldBe` Just ()+          sp incipit "\nXXX: "  `shouldBe` Just ()+    it "doesn't work without previous newline" $+      sp incipit "\nTODO:some"  `shouldBe` Nothing+    it "doesn't work if you don't add a space after :" $+      sp incipit "\nTODO:some"  `shouldBe` Nothing+    it "does allow you to omit the :" $+      sp incipit "\nxxx some"  `shouldBe` Just ()+    it "is case unsensitive in flag-word" $+      sp incipit "\ntOdO some"  `shouldBe` Just ()+    it "allows whitespace before the todo" $+      sp incipit "\n   fixme some"  `shouldBe` Just ()+    it "doesn't allow anything else before flag-words" $+      sp incipit "\nso fixme someday"  `shouldBe` Nothing+    it "fails if we are not at the beginning of line" $+      sp incipit "\nso fixme someday"  `shouldBe` Nothing++  describe "tags" $ do+    it "parses a series of tags, sep by whitespace" $+      sp tags " [tag] [field:test] [tog]"+         `shouldBe` Just [Tag "tag", Tag "field:test", Tag "tog"]+    it "allows ending whitespace" $+      sp tags "   [tag] [field:test] [tog] "+         `shouldBe` Just [Tag "tag", Tag "field:test", Tag "tog"]++  describe "freeText" $ do+    it "parses a free form text" $+      sp freeText "this is it\n\n" `shouldBe` Just "this is it"+    it "can be ended by tags/fields" $+      sp freeText "this is it [field:val]" `shouldBe` Just "this is it"+    it "trims extra whitespace" $+      sp freeText "this is  it [bug]" `shouldBe` Just "this is it"+    it "cannot be ended by new issue on same line" $+      sp freeText "this is it   TODO: desc\n" `shouldBe`+             Just "this is it TODO: desc"+    it "cannot be ended by new issue on new-but-not-beginning-of-line" $+      sp freeText "this is it \nn  TODO: desc\n" `shouldBe`+             Just "this is it n TODO: desc"+    it "can be ended by blank line" $+      sp freeText "this is it\n\n  TODO: desc" `shouldBe` Just "this is it"+    it "can be ended by incomplete blank line (eof)" $+      sp freeText "this is it\n" `shouldBe` Just "this is it"++  describe "issue" $ do+    it "parses an issue" $+      sp issue "\n fixMe this is it [t] [f:w]"+         `shouldBe` (Just $ Issue "<f>" 2 "this is it" [Tag "t", Tag "f:w"])+    it "fails if were are not at beginning of a line" $+      sp issue "fixMe this is it [t] [f:w]"+         `shouldBe` Nothing+    it "parses tagless/fieldless issues too" $+      sp issue "\nTODO: this is it\n"+         `shouldBe` (Just $ Issue "<f>" 2 "this is it" [])+    it "doesn't display the eventual \n at eof" $+      sp issue "\nTOdO: this is it\n"+         `shouldBe` (Just $ Issue "<f>" 2 "this is it" [])+    it "doesn't accept a lone (naked) todo" $+      sp issue "\ntodo\n"+         `shouldBe` Nothing++  describe "issues" $ do+    it "parses multiple issues" $+      sp issues "\nTODO: this is it [f:w]\n TODO: hey [t]"+         `shouldBe` Just [Issue "<f>" 1 "this is it" [Tag "f:w"],+                          Issue "<f>" 2 "hey" [Tag "t"]]+    it "don't parse multiple issues if on the same line" $+      sp issues "\nTODO: this is it [f:w] TODO: hey [t]"+         `shouldBe` Just [Issue "<f>" 1 "this is it" [Tag "f:w"]]++  describe "issueFinder" $ do+    it "reads a file (code or txt) for issues" $+      let fp = "test/test-files/lang-comm/test.txt" in+      issueFinder [fp] >>= \i ->+      i `shouldBe` [Issue fp 1 "palla" [],+                    Issue fp 3 "beta"  [],+                    Issue fp 4 "gamma" [],+                    Issue fp 6 "qqq"   [Tag "bug"]]+
+ test/Lentil/Parse/SourceSpec.hs view
@@ -0,0 +1,112 @@+module Lentil.Parse.SourceSpec where++import Test.Hspec+import Text.Parsec (runParser)+import Text.Parsec.Char+import Text.Parsec.Combinator+import Control.Applicative++import Lentil.Parse.Source++-- Parsing tests++-- simple parser+sp :: ParSource a -> String -> Maybe a+sp p cs = either (const Nothing) Just+                 (runParser p () fp cs)+    where fp = "<f>"+++main :: IO ()+main = hspec spec+++spec :: Spec+spec = do++  describe "manyTill1" $ do+    it "behaves like manyTill on non-empty string" $+      sp (manyTill1 anyChar newline) "foo\n" `shouldBe`+      sp (manyTill anyChar newline) "foo\n"+    it "doesn't runtime crash on a empty string (instead returns [])" $+      sp (manyTill1 anyChar newline) "" `shouldBe` Just []++  describe "lineComment" $ do+    it "parses a line comment" $+      sp (lineComment "//") "// Test\n" `shouldBe` Just " Test\n"+    it "fails without trailing \\n" $+      sp (lineComment "//") "// Test"   `shouldBe` Nothing++  describe "blockComment" $ do+    it "parses a block comment" $+      sp (blockComment ("/*","*/")) "/* Test\n2 */" `shouldBe`+         Just " Test\n2 "++  describe "litString" $ do+    it "parses code string" $+      sp (litString '"') "\"palla\"" `shouldBe` Just "palla"+    it "parses code string with escaped \" inside" $+      sp (litString '"') "\"pal\\\"la\"" `shouldBe` Just "pal\"la"+    it "parses code string with comments symbols inside" $+      sp (litString '"') "\"pal#la\"" `shouldBe` Just "pal#la"++  describe "litChar" $ do+    it "parses a string literal character inside" $+      sp (litChar '\'') "'a'" `shouldBe`  Just 'a'+    it "parses escaped characters too" $+      sp (litChar '\'') "'\"'" `shouldBe` Just '\"'++  let hss = ParSyntax ["--"] [("{-", "-}")] ['"']  ['\'']+  describe "program" $ do+    it "parses program instructions till eof" $+      sp (program hss) "prova " `shouldBe`  Just "prova "+    it "stops at single-line comment" $+      sp (program hss) "prova -- " `shouldBe`  Just "prova "+    it "stops at blockcomment" $+      sp (program hss) "prova {- " `shouldBe`  Just "prova "+    it "stops at literal string" $+      sp (program hss) "prova \"babby " `shouldBe`  Just "prova "+    it "stops at literal char" $+      sp (program hss) "prova ' '" `shouldBe`  Just "prova "+    it "stops at ' which is not a literal char" $+      sp (program hss) "prova' " `shouldBe`  Just "prova' "++  let rbs = ParSyntax ["#"] [] ['"', '\'']  []+  describe "source" $ do+    it "parses one piece of source (line-comment)" $+      sp (source hss) "-- hey\n my " `shouldBe`  Just " hey\n"+    it "parses one piece of source (block-comment)" $+      sp (source hss) "{-hey-}\n my " `shouldBe`  Just "hey"+    it "parses one piece of source (string-literal)" $+      sp (source hss) "\"hey\"" `shouldBe`  Just ""+    it "parses a string for language with ' and \" available" $+      sp (source rbs) "\"he#y\"" `shouldBe`  Just ""+    it "parses one piece of source (char-literal)" $+      sp (source hss) "\'h\'" `shouldBe`  Just ""+    it "parses one piece of source (program instructions)" $+      sp (source hss) "prime'" `shouldBe`  Just ""++  describe "commentParser" $ do+    beforeAll (readFile "test/test-files/lang-comm/out.blocks") $ do+      it "parses a haskell source" (\a ->+        commentParser "test/test-files/lang-comm/haskell.hs" >>= \s ->+        s `shouldBe` Just a)+      it "parses a C source" (\a ->+        commentParser "test/test-files/lang-comm/clang.c" >>= \s ->+        s `shouldBe` Just a)+      it "parses a Pascal source" (\a ->+        commentParser "test/test-files/lang-comm/pascal.pas" >>= \s ->+        s `shouldBe` Just a)+      it "parses a plain text source" (\a ->+        commentParser "test/test-files/lang-comm/text.txt" >>= \s ->+        s `shouldBe` Just a)+      it "parses a javascript source" (\a ->+        commentParser "test/test-files/lang-comm/javascript.js" >>= \s ->+        s `shouldBe` Just a)+      it "parses a python source" (\a ->+        commentParser "test/test-files/lang-comm/python.py" >>= \s ->+        s `shouldBe` Just a)+      it "parses a ruby source" (\a ->+        commentParser "test/test-files/lang-comm/ruby.rb" >>= \s ->+        s `shouldBe` Just a)+
+ test/Lentil/PrintSpec.hs view
@@ -0,0 +1,20 @@+module Lentil.PrintSpec where++import Test.Hspec++-- import Lentil.Types+import Lentil.Print++-- Parsing tests++main :: IO ()+main = hspec spec+++spec :: Spec+spec = do+++  describe "alignNumber" $ do+    it "right aligns an integer" $+      alignNumber  4 30 `shouldBe` "  30"
+ test/Lentil/QuerySpec.hs view
@@ -0,0 +1,80 @@+module Lentil.QuerySpec where++import Test.Hspec++import Lentil.Types+import Lentil.Query++-- Parsing tests++main :: IO ()+main = hspec spec+++spec :: Spec+spec = do++  let i1 = Issue "a.hs" 1 "issue 1"  [Tag "ta", Tag "fl:va"]+      i2 = Issue "a.hs" 2 "issue 12" [Tag "tb"]+      i3 = Issue "b.hs" 1 "issue 3"  [Tag "tb", Tag "fl:vb"]+      ix = Issue "b.hs" 1 "issue n"  []+      is = [i1, i2, i3]++  describe "filter*" $ do+    it "filters by Filepath" $+      filterFilepath "a.hs" is `shouldBe` [i1, i2]+    it "filters by Description" $+      filterDescription "3" is `shouldBe` [i3]+    it "filters by Tag" $+      filterTags "tb" is `shouldBe` [i2, i3]+    it "filters by Tag 'label'" $+      filterTags "fl:" is `shouldBe` [i1, i3]+    it "filters by using regexps" $+      filterTags "(ta|tb)" is `shouldBe` [i1, i2, i3]++  describe "filterTagless" $ do+    it "filters by empty values" $+      filterTagless (ix:is) `shouldBe` [ix]++  describe "negFilter" $ do+    it "negates a filter" $+      negFilter (filterTags "fl:") is `shouldBe` [i2]+    it "cancels itself on a double negation" $+      negFilter (negFilter (filterTags "fl:")) is+                `shouldBe` filterTags "fl:" is++  describe "filter(And|Or)" $ do+    it "chains (boolean and) multiple filters" $+      filterAnd [filterFilepath "a.hs", filterTags "b"] is+                `shouldBe` [i2]+    it "chains (boolean or) multiple filters" $+      filterOr [filterFilepath "a.hs", filterTags "b"] is+                `shouldBe` [i1, i2, i3]++  describe "sortIssues" $ do+    it "sorts Issues" $+      sortIssues iFile Asc is `shouldBe` [i1, i2, i3]+    it "sorts Issues in descending order" $+      sortIssues iFile Desc is `shouldBe` [i3, i2, i1]+    it "smart sorts strings containing naturals" $+      sortIssues iDesc Asc is `shouldBe` [i1, i3, i2]++  describe "sortTag" $ do+    it "sorts by partial tag, empty fields first" $+      sortTag "fl:" Asc is `shouldBe` [i2, i1, i3]++  describe "chainSorts" $ do+    it "sequences sort functions" $+      chainSorts is [sortIssues iFile Desc,+                     sortIssues iDesc Desc]+                 `shouldBe` [i3, i2, i1]++  describe "groupIssues" $ do+    it "group isses" $+      groupIssues iFile is `shouldBe` [[i1, i2], [i3]]++  describe "tagPop" $ do+    it "outputs tag popularity list" $+      tagPop is `shouldBe` [(Tag "tb", 2), (Tag "ta", 1),+                            (Tag "fl:vb", 1), (Tag "fl:va", 1)]+
+ test/test-files/lang-comm/clang.c view
@@ -0,0 +1,11 @@+// single+// comment++var c = "st/*ring"; // string++/* block+   comment */++char letter = 'c' // char++//  end
+ test/test-files/lang-comm/haskell.hs view
@@ -0,0 +1,11 @@+-- single+-- comment++someOther = "  \"  " -- string++{- block+   comment -}++char' = '\n' -- char++--  end
+ test/test-files/lang-comm/javascript.js view
@@ -0,0 +1,11 @@+// single+// comment++var c = "st/*ring"; // string++/* block+   comment */++var letter = 'c' // char++//  end
+ test/test-files/lang-comm/out.blocks view
@@ -0,0 +1,12 @@++ single+ comment++ string++ block+   comment ++ char++  end
+ test/test-files/lang-comm/pascal.pas view
@@ -0,0 +1,11 @@+// single+// comment++qqq := 'Afla' // string++{ block}+(*   comment *)++c := '"' // char++//  end
+ test/test-files/lang-comm/python.py view
@@ -0,0 +1,11 @@+# single+# comment++c = "st/*ring" # string++""" block+   comment """++letter = 'c' # char++#  end
+ test/test-files/lang-comm/ruby.rb view
@@ -0,0 +1,11 @@+# single+# comment++puts "str#ing" # string++=begin block+   comment =end++a = [1, 'hi', 3.14, 1, 2, [4, 5]] # char++#  end
+ test/test-files/lang-comm/test.txt view
@@ -0,0 +1,9 @@+TODO: palla++fixme beta+xxx gamma++fixme qqq [bug]+gaeta+    +
+ test/test-files/lang-comm/text.txt view
@@ -0,0 +1,11 @@+ single+ comment++ string++ block+   comment ++ char++  end
+ test/test-files/test-proj/fold-a/foo1.hs view
@@ -0,0 +1,1 @@+fac' c = product [1..c] -- TODO: sometodo [t]
+ test/test-files/test-proj/fold-b/foo2.hs view
@@ -0,0 +1,1 @@+-- TODO: sometodo [t]
+ test/test-files/test-proj/fold-c/foo3.hs view
@@ -0,0 +1,1 @@+-- TODO: sometodo [t]
+ test/test-files/test-proj/fold-c/sub-fold/foo4.hs view
@@ -0,0 +1,1 @@+-- TODO: sometodo [t]
+ test/test-files/test-proj/fold-c/sub-fold/foo5.hs view
@@ -0,0 +1,1 @@+-- TODO: sometodo [t]