diff --git a/lentil.cabal b/lentil.cabal
--- a/lentil.cabal
+++ b/lentil.cabal
@@ -1,5 +1,5 @@
 name:                lentil
-version:             0.1.0.0
+version:             0.1.0.1
 synopsis:            frugal issue tracker
 description:         minumum effort, cohesive issue tracker for the rest of us.
                      Check homepage for manual, tutorial.
@@ -23,6 +23,9 @@
                        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
 
   hs-source-dirs:      src
 
diff --git a/src/Lentil/Args.hs b/src/Lentil/Args.hs
new file mode 100644
--- /dev/null
+++ b/src/Lentil/Args.hs
@@ -0,0 +1,171 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Lentil.Query
+-- Copyright   :  © 2015 Francesco Ariis
+-- License     :  GPLv3 (see the LICENSE file)
+--
+-- Command line parsing
+-----------------------------------------------------------------------------
+
+module Lentil.Args where
+
+import Lentil.Types
+import Lentil.Query
+
+import Options.Applicative
+import qualified Data.Char as C
+
+
+-----------
+-- TYPES --
+-----------
+
+-- TODO: show help option in lentil short help [feature:intermediate]
+-- TODO: disambiguation optparse-applicative [feature:intermediate]
+-- TODO: help as lentil PATH [ PATH... ] [ OPTIONS ] [feature:intermediate]
+
+data LOptions = LOptions { loInExcl   :: ([FilePath], [FilePath]),
+                           loFormat   :: Format,
+                           loFilters  :: [LFilter],
+                           loSort     :: [LSort],
+                           loOutFile  :: Maybe FilePath }
+              deriving (Show)
+
+type LFilter = [Issue] -> [Issue]
+type ChType  = [LFilter] -> [Issue] -> [Issue] -- AND or OR chain
+type LSort   = [Issue] -> [Issue]
+
+
+lOpts :: Parser LOptions
+lOpts = LOptions <$> inexcls <*> format <*> filters <*>
+                     issort <*> outfile
+    where inexcls = (,) <$> includes <*> many exclude
+          filters = many $ foldl1 (<|>) [tag, notag, path,
+                                         nopath, desc, nodesc]
+
+
+-------------
+-- PARSERS --
+-------------
+
+-- argument "." gets replaced to "" (all files)
+includes :: Parser [FilePath]
+includes = map repf <$> some (argument str (metavar "PATH..."))
+    where repf "." = ""
+          repf cs  = cs
+
+exclude :: Parser FilePath
+exclude = strOption ( short 'x'      <>
+                      metavar "PATH" <>
+                      help "file/directory to exclude" )
+
+format :: Parser Format
+format = option (str >>= parseFormat)
+                    ( short 'f'      <>
+                      metavar "TYPE" <>
+                      value Pretty   <>
+                      help "output format (pretty, tagpop, csv)" )
+    where
+          parseFormat :: String -> ReadM Format
+          parseFormat s = let asl = map forTup (enumFrom minBound)
+                          in maybe (rerr s "unrecognised format")
+                                   return (lookup s asl)
+
+          forTup f      = (map C.toLower $ show f, f)
+
+
+outfile :: Parser (Maybe FilePath)
+outfile = optional $ strOption
+                    ( long "output"  <>
+                      metavar "FILE" <>
+                      help "output file (if not present, prints to stdout)" )
+
+
+
+issort :: Parser [LSort]
+issort = pure []
+
+{-
+
+    TODO: uncomment and implement sorting [feature:intermediate]
+
+issort :: Parser [LSort]
+issort = option (str >>= parseSorts)
+                 ( long "sort"                       <>
+                   short 's'                         <>
+                   metavar "[!]ORDER[,[!]ORDER ...]" <>
+                   value []   <>
+                   help "sort order, comma separated (path, desc, label). \
+                        \'!' orders in descending fashion. Check manual for \
+                        \examples" )
+          -- TODO: sort parsing? [feature:intermediate]
+    where parseSorts :: String -> ReadM [LSort]
+          parseSorts cs = mapM parSort . words . replace ',' ' ' $ cs
+
+          -- this parsort is broken [bug] [duct]
+          parSort :: String -> ReadM LSort
+          parSort "path"  = return (sortIssues iFile Asc)
+          parSort "desc"  = return (sortIssues iDesc Asc)
+          parSort "!path" = return (sortIssues iFile Desc)
+          parSort "!desc" = return (sortIssues iDesc Desc)
+          parSort _       = error "lol"
+
+          replace :: Char -> Char -> String -> String
+          replace a b cs = map (\c -> if c == a then b else c) cs
+
+-}
+
+-------------------
+-- FILTER PARAMS --
+-------------------
+
+path :: Parser LFilter
+path = option (filterFilepath <$> str)
+                       ( short 'p'        <>
+                         metavar "EXPR"   <>
+                         help "filters for filepath matching EXPR" )
+
+nopath :: Parser LFilter
+nopath = option (negFilter . filterFilepath <$> str)
+                         ( short 'P'        <>
+                           metavar "EXPR"   <>
+                           help "filters for filepath NOT matching EXPR" )
+
+desc :: Parser LFilter
+desc = option (filterDescription <$> str)
+                       ( short 'd'        <>
+                         metavar "EXPR"   <>
+                         help "filters for description matching EXPR" )
+
+nodesc :: Parser LFilter
+nodesc = option (negFilter . filterDescription <$> str)
+                       ( short 'D'        <>
+                         metavar "EXPR"   <>
+                         help "filters for description NOT matching EXPR" )
+
+tag :: Parser LFilter
+tag = option optTxt ( short 't'       <>
+                      metavar "EXPR"  <>
+                      help "filter for tag matching EXPR" )
+
+notag :: Parser LFilter
+notag = option (negFilter <$> optTxt)
+                       ( short 'T'       <>
+                         metavar "EXPR"  <>
+                         help "filter for tag NOT matching EXPR" )
+
+
+optTxt :: ReadM LFilter
+optTxt = str >>= \oStr ->
+           if oStr == "^"
+             then return filterTagless
+             else return (filterTags oStr)
+
+
+-----------------
+-- ANCILLARIES --
+-----------------
+
+rerr :: String -> String -> ReadM a
+rerr var msg = readerError $ msg ++ " \"" ++ var ++ "\""
+
diff --git a/src/Lentil/Export.hs b/src/Lentil/Export.hs
new file mode 100644
--- /dev/null
+++ b/src/Lentil/Export.hs
@@ -0,0 +1,35 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Lentil.Export
+-- Copyright   :  © 2015 Francesco Ariis
+-- License     :  GPLv3 (see the LICENSE file)
+--
+-- Exporting issues to various formats
+-----------------------------------------------------------------------------
+
+module Lentil.Export where
+
+import Text.CSV
+import Lentil.Types
+
+import qualified Data.List as L
+
+
+---------------
+-- FUNCTIONS --
+---------------
+
+issues2CSV :: [Issue] -> String
+issues2CSV is = printCSV (firstLine : map i2r is)
+    where firstLine = ["Filepath", "Row", "Description", "Tags"]
+          i2r i     = [iFile i, show (iRow i),
+                       iDesc i, tags2String (iTags i)]
+
+
+-----------------
+-- ANCILLARIES --
+-----------------
+
+tags2String :: [Tag] -> String
+tags2String ts = L.intercalate " " (map tagString ts)
+
diff --git a/src/Lentil/File.hs b/src/Lentil/File.hs
new file mode 100644
--- /dev/null
+++ b/src/Lentil/File.hs
@@ -0,0 +1,50 @@
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Lentil.File
+-- Copyright   :  © 2015 Francesco Ariis
+-- License     :  GPLv3 (see the LICENSE file)
+--
+-- File operations
+-----------------------------------------------------------------------------
+
+module Lentil.File where
+
+import Lentil.Types
+import Lentil.Parse
+
+import System.FilePath
+import System.FilePath.Find
+import Data.Monoid
+import Control.Applicative
+
+import qualified Data.List as L
+
+
+
+---------------
+-- INSTANCES --
+---------------
+
+instance Monoid a => Monoid (FindClause a) where
+    mempty = pure mempty
+    mappend = liftA2 mappend
+
+
+--------------
+-- FILESCAN --
+--------------
+
+findIssues :: [FilePath] -> [FilePath] -> IO [Issue]
+findIssues is xs = find always (findClause is xs) "." >>= issueFinder
+
+-- fp to include, fp to exclude, clause
+findClause :: [FilePath] -> [FilePath] -> FindClause Bool
+findClause i x = let ic     = mconcat $ map fp2fc i
+                     xc     = mconcat $ map fp2fc x
+                 in fmap getAny ic &&? (not <$> fmap getAny xc)
+    where
+          fp2fc :: FilePath -> FindClause Any
+          fp2fc f = Any . L.isPrefixOf (combine "." f) <$> filePath
+          -- TODO: combine funziona su windows? [feature:intermediate]
+
diff --git a/src/Lentil/Print.hs b/src/Lentil/Print.hs
new file mode 100644
--- /dev/null
+++ b/src/Lentil/Print.hs
@@ -0,0 +1,93 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Lentil.Print
+-- Copyright   :  © 2015 Francesco Ariis
+-- License     :  GPLv3 (see the LICENSE file)
+--
+-- printing types
+-----------------------------------------------------------------------------
+
+module Lentil.Print where
+
+import Lentil.Types
+import Lentil.Query
+
+import Text.PrettyPrint.ANSI.Leijen as PP
+import qualified Data.List as L
+
+
+-- number of spaces + digits in indentation levels
+indNum :: Int
+indNum = 6
+
+----------------
+-- PRIMITIVES --
+----------------
+
+-- right align a number in a space of i columns
+alignNumber :: Int -> Int -> String
+alignNumber i n = replicate (i - length sn) ' ' ++ sn
+    where sn = show n
+
+-- Bool: wheter or not to use colours in rendering
+myRender :: Bool -> Doc -> String
+myRender col d = ($ "") . displayS . renderPretty 1 75 $ d'
+    where d' | col == True = d
+             | otherwise   = plain d
+
+
+------------------
+-- PRETTY PRINT --
+------------------
+
+data TagCol = Red | Blue deriving (Eq)
+ppTag :: TagCol -> Tag -> Doc
+ppTag c t = col (char openDel) <> string (tagString t) <>
+            col (char closeDel)
+    where col | c == Red  = red
+              | c == Blue = blue
+
+ppIssue :: Int -> Issue -> Doc
+ppIssue ind is = indent spInd ( fillSep [string iNum, string "",
+                                         hang 0 (fillSep $ ppDescTags is)] )
+    where
+          ppDescTags :: Issue -> [Doc]
+          ppDescTags i = map string (words (iDesc is)) ++
+                         map (ppTag Blue) (iTags i)
+
+          spInd = indNum - ind
+          iNum  = alignNumber ind $ iRow is
+
+
+ppFile :: Int -> [Issue] -> Doc
+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
+          ind = maximum . map (length . show . iRow) $ is
+          igr = map (ppFile ind) $ groupIssues iFile is
+
+
+-------------
+-- REPORTS --
+-------------
+
+-- tagpop pp report
+ppPopularity :: Bool -> [Issue] -> String
+ppPopularity col is = myRender col $ text "Tags popularity:" PP.<$>
+                                     vsep (ppPops) -- PP.<$> (string "")
+    where
+          listpop = tagPop is
+
+          ind   = maximum . map (length . show . snd) $ listpop
+          spInd = indNum - ind
+
+          ppPop (t, n) = indent spInd (fillSep [iNum n, string "",
+                                                hang 0 (ppTag Red t)])
+          ppPops       = map ppPop listpop
+
+          iNum n = string $ alignNumber ind n
+
diff --git a/src/Lentil/Query.hs b/src/Lentil/Query.hs
new file mode 100644
--- /dev/null
+++ b/src/Lentil/Query.hs
@@ -0,0 +1,100 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Lentil.Query
+-- Copyright   :  © 2015 Francesco Ariis
+-- License     :  GPLv3 (see the LICENSE file)
+--
+-- Query Issues
+-----------------------------------------------------------------------------
+
+module Lentil.Query where
+
+import Lentil.Types
+
+import Text.Regex.TDFA
+import qualified Data.List as L
+import qualified Data.Function as F
+import qualified Algorithms.NaturalSort as NS
+
+type RegExp = String
+
+-- TODO: filtering case unsensitive? [test]
+
+---------------
+-- FILTERING --
+---------------
+
+-- chains (boolean `and`) some partially applied filters
+filterAnd :: [[Issue] -> [Issue]] -> [Issue] -> [Issue]
+filterAnd fs is = foldl (\r f -> L.intersect r (f is)) is fs
+
+-- chains (boolean `or`) some partially applied filters
+filterOr :: [[Issue] -> [Issue]] -> [Issue] -> [Issue]
+filterOr fs is = foldl (\r f -> L.union r (f is)) [] fs
+
+filterFilepath :: RegExp -> [Issue] -> [Issue]
+filterFilepath r is = filter ((=~ r) . iFile) is
+
+filterDescription :: RegExp -> [Issue] -> [Issue]
+filterDescription r is = filter ((=~ r) . iDesc) is
+
+filterTags :: RegExp -> [Issue] -> [Issue]
+filterTags r is = filter (df . iTags) is
+    where df ts = any ((=~ r) . tagString) ts
+
+filterTagless :: [Issue] -> [Issue]
+filterTagless is = filter (null . iTags) is
+
+negFilter :: ([Issue] -> [Issue]) -> [Issue] -> [Issue]
+negFilter f is = is L.\\ f is
+
+
+-------------
+-- SORTING --
+-------------
+
+data SortOrder = Asc | Desc deriving (Show, Eq)
+
+-- sequences sorting
+chainSorts :: [Issue] -> [[Issue] -> [Issue]] -> [Issue]
+chainSorts is []     = is
+chainSorts is (s:ss) = concatMap (flip chainSorts ss) $ L.group (s is)
+
+-- given a particular accessor and a sort order, sorts issues
+sortIssues :: (NS.NaturalSort a) => (Issue -> a) -> SortOrder ->
+                                    [Issue] -> [Issue]
+sortIssues ax o is = if o == Asc then sorted else reverse sorted
+    where sorted = L.sortBy (NS.compare `F.on` ax)  is
+
+-- give a *partial* tag and it will sort the rest of it
+sortTag :: RegExp -> SortOrder -> [Issue] -> [Issue]
+sortTag r o is = if o == Asc then sorted else reverse sorted
+    where
+          ts2s :: RegExp -> [Tag] -> String
+          ts2s e ts = maybe "" id $ L.find (=~ e) (map tagString ts)
+
+          cf :: Issue -> Issue -> Ordering
+          cf a b = (NS.compare `F.on` (ts2s r . iTags)) a b
+
+          sorted = L.sortBy cf is
+
+
+--------------
+-- GROUPING --
+--------------
+
+groupIssues :: (NS.NaturalSort a, Eq a) =>
+               (Issue -> a) -> [Issue] -> [[Issue]]
+groupIssues ax is = L.groupBy ((==) `F.on` ax) . sortIssues ax Asc $ is
+
+
+-------------
+-- REPORTS --
+-------------
+
+-- tag popularity
+tagPop :: [Issue] -> [(Tag, Int)]
+tagPop is = reverse . L.sortBy (compare `F.on` snd) .
+            map (\l -> (head l, length l)) . L.group .
+            L.sort . concatMap iTags $ is
+
diff --git a/src/Lentil/Types.hs b/src/Lentil/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Lentil/Types.hs
@@ -0,0 +1,35 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Lentil.Types
+-- Copyright   :  © 2015 Francesco Ariis
+-- License     :  GPLv3 (see the LICENSE file)
+--
+-- Types descriptions
+-----------------------------------------------------------------------------
+
+module Lentil.Types where
+
+
+
+data Issue = Issue { iFile   :: FilePath,
+                     iRow    :: Row,
+                     iDesc   :: Description,
+                     iTags   :: [Tag] }
+             deriving (Eq, Show)
+
+data Tag   = Tag { tagString :: String }
+             deriving (Show, Eq, Ord)
+
+type Description = String
+type Row = Int
+
+-- output format
+data Format = TagPop | Pretty | Csv
+            deriving (Show, Eq, Enum, Bounded)
+
+
+-- tag delimiters
+openDel, closeDel :: Char
+openDel  = '['
+closeDel = ']'
+
