toodles 0.1.0.16 → 0.1.1
raw patch · 10 files changed
+167/−61 lines, 10 files
Files
- LICENSE +21/−0
- README.md +1/−0
- app/Config.hs +18/−6
- app/Main.hs +5/−5
- app/Parse.hs +30/−15
- app/Server.hs +20/−13
- app/Types.hs +56/−10
- toodles.cabal +9/−7
- web/html/index.html +3/−1
- web/js/app.js +4/−4
+ LICENSE view
@@ -0,0 +1,21 @@+MIT License++Copyright (c) 2018 Avi Press++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
README.md view
@@ -51,6 +51,7 @@ Currently via config you can: - Set files to ignore via a list of regular expressions+- Specify your own flags to scan for other than the built-ins (TODO, FIXME, XXX) ### Scanned Languages
app/Config.hs view
@@ -1,23 +1,34 @@-{-# LANGUAGE DeriveDataTypeable,- DataKinds,- OverloadedStrings,- ScopedTypeVariables,- TypeOperators #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeOperators #-} -module Config where+module Config+ ( toodlesArgs+ , ToodlesArgs(..)+ , SearchFilter(..)+ , AssigneeFilterRegex(..)+ )+ where import Paths_toodles+import Types import Data.Text (Text) import Data.Version (showVersion) import System.Console.CmdArgs +toodlesArgs :: IO ToodlesArgs+toodlesArgs = cmdArgs argParser+ data ToodlesArgs = ToodlesArgs { directory :: FilePath , assignee_search :: Maybe SearchFilter , limit_results :: Int , port :: Maybe Int , no_server :: Bool+ , userFlag :: [UserFlag] } deriving (Show, Data, Typeable, Eq) newtype SearchFilter =@@ -34,6 +45,7 @@ , limit_results = def &= help "Limit number of search results" , port = def &= help "Run server on port" , no_server = def &= help "Output matching todos to the command line and exit"+ , userFlag = def &= help "Additional flagword (e.g.: MAYBE)" } &= summary ("toodles " ++ showVersion version) &= program "toodles" &= verbosity
app/Main.hs view
@@ -11,15 +11,14 @@ import Data.Maybe (fromMaybe) import qualified Data.Text as T (unpack) import Network.Wai.Handler.Warp (run)-import System.Console.CmdArgs (cmdArgs) import Text.Printf (printf) main :: IO () main = do- userArgs <- cmdArgs argParser >>= setAbsolutePath+ userArgs <- toodlesArgs >>= setAbsolutePath sResults <- runFullSearch userArgs case userArgs of- (ToodlesArgs _ _ _ _ True) -> mapM_ (putStrLn . prettyFormat) $ todos sResults+ (ToodlesArgs _ _ _ _ True _) -> mapM_ (putStrLn . prettyFormat) $ todos sResults _ -> do let webPort = fromMaybe 9001 $ port userArgs ref <- newIORef sResults@@ -28,12 +27,13 @@ run webPort $ app $ ToodlesState ref dataDir prettyFormat :: TodoEntry -> String-prettyFormat (TodoEntryHead _ l a p n entryPriority _ _ _) =+prettyFormat (TodoEntryHead _ l a p n entryPriority f _ _ _) = printf- "Assignee: %s\n%s%s:%d\n%s"+ "Assignee: %s\n%s%s:%d\n%s - %s" (fromMaybe "None" a) (maybe "" (\x -> "Priority: " ++ show x ++ "\n") entryPriority) p n+ (show f) (unlines $ map T.unpack l) prettyFormat (TodoBodyLine _) = error "Invalid type for prettyFormat"
app/Parse.hs view
@@ -63,6 +63,21 @@ entryTags = filter (T.isPrefixOf "#") dataTokens in (assigneeTo, priorityVal, filteredDetails, entryTags) +-- | parse "hard-coded" flags, and user-defined flags if any+parseFlag :: [UserFlag] -> Parser Flag+parseFlag us = foldr (\a b -> b <|> foo a) (try parseFlagHardcoded) us+ where+ foo :: UserFlag -> Parser Flag+ foo (UserFlag x) = try (symbol x *> pure (UF $ UserFlag x))++-- | parse flags TODO, FIXME, XXX+parseFlagHardcoded :: Parser Flag+parseFlagHardcoded =+ try (symbol "TODO" *> pure TODO )+ <|> try (symbol "FIXME" *> pure FIXME)+ <|> (symbol "XXX" *> pure XXX )++ fileTypeToComment :: [(Text, Text)] fileTypeToComment = [ (".c", "//")@@ -102,16 +117,16 @@ , (".yaml", "#") ] -runTodoParser :: SourceFile -> [TodoEntry]-runTodoParser (SourceFile path ls) =+runTodoParser :: [UserFlag] -> SourceFile -> [TodoEntry]+runTodoParser us (SourceFile path ls) = let parsedTodoLines = map- (\(lineNum, lineText) -> parseMaybe (parseTodo path lineNum) lineText)+ (\(lineNum, lineText) -> parseMaybe (parseTodo us path lineNum) lineText) (zip [1 ..] ls) groupedTodos = foldl foldTodoHelper ([], False) parsedTodoLines in fst groupedTodos - where+ where -- fold fn to concatenate todos that a multiple, single line comments foldTodoHelper :: ([TodoEntry], Bool) -> Maybe TodoEntry -> ([TodoEntry], Bool) foldTodoHelper (todoEntries, currentlyBuildingTodoLines) maybeTodo@@ -124,7 +139,7 @@ (init todoEntries ++ [combineTodo (last todoEntries) (fromJust maybeTodo)], True) | otherwise = (todoEntries, False) - where+ where isEntryHead :: TodoEntry -> Bool isEntryHead TodoEntryHead {} = True isEntryHead _ = False@@ -134,8 +149,8 @@ isBodyLine _ = False combineTodo :: TodoEntry -> TodoEntry -> TodoEntry- combineTodo (TodoEntryHead i b a p n entryPriority attrs entryTags entryLeadingText) (TodoBodyLine l) =- TodoEntryHead i (b ++ [l]) a p n entryPriority attrs entryTags entryLeadingText+ combineTodo (TodoEntryHead i b a p n entryPriority f attrs entryTags entryLeadingText) (TodoBodyLine l) =+ TodoEntryHead i (b ++ [l]) a p n entryPriority f attrs entryTags entryLeadingText combineTodo _ _ = error "Can't combine todoEntry of these types" getExtension :: FilePath -> Text@@ -162,15 +177,14 @@ unkownMarker :: Text unkownMarker = "UNKNOWN-DELIMETER-UNKNOWN-DELIMETER-UNKNOWN-DELIMETER" -parseTodo :: FilePath -> LineNumber -> Parser TodoEntry-parseTodo path lineNum = try parseTodoEntryHead- <|> parseComment (getExtension path)-- where- parseTodoEntryHead :: Parser TodoEntry- parseTodoEntryHead = do+parseTodo :: [UserFlag] -> FilePath -> LineNumber -> Parser TodoEntry+parseTodo us path lineNum = try (parseTodoEntryHead us)+ <|> parseComment (getExtension path)+ where+ parseTodoEntryHead :: [UserFlag] -> Parser TodoEntry+ parseTodoEntryHead uf = do entryLeadingText <- manyTill anyChar (prefixParserForFileType $ getExtension path)- _ <- symbol "TODO"+ f <- parseFlag uf entryDetails <- optional $ try (inParens $ many (noneOf [')', '('])) let parsedDetails = parseDetails . T.pack <$> entryDetails entryPriority = (readMaybe . T.unpack) =<< (snd4 =<< parsedDetails)@@ -187,6 +201,7 @@ path lineNum entryPriority+ f otherDetails entryTags (T.pack entryLeadingText)
app/Server.hs view
@@ -26,7 +26,6 @@ import qualified Data.Yaml as Y import GHC.Generics (Generic) import Servant-import System.Console.CmdArgs import System.Directory import System.IO.HVFS import qualified System.IO.Strict as SIO@@ -37,8 +36,9 @@ import Text.Printf import Text.Regex.Posix -newtype ToodlesConfig = ToodlesConfig+data ToodlesConfig = ToodlesConfig { ignore :: [FilePath]+ , flags :: [UserFlag] } deriving (Show, Generic, FromJSON) app :: ToodlesState -> Application@@ -115,7 +115,7 @@ let comment = fromJust $ lookup ("." <> getExtension (sourceFile t)) fileTypeToComment detail =- "TODO (" <>+ renderFlag (flag t) <> " (" <> (T.pack $ Data.String.Utils.join "|"@@ -129,7 +129,7 @@ mapHead (\l -> leadingText t <> l) $ mapInit (\l -> foldl (<>) "" [" " | _ <- [1..(T.length $ leadingText t)]] <> l) commented - where+ where mapHead :: (a -> a) -> [a] -> [a] mapHead f (x:xs) = f x : xs mapHead _ xs = xs@@ -142,6 +142,12 @@ listIfNotNull "" = [] listIfNotNull s = [s] + renderFlag :: Flag -> Text+ renderFlag TODO = "TODO"+ renderFlag FIXME = "FIXME"+ renderFlag XXX = "XXX"+ renderFlag (UF (UserFlag x)) = x+ -- | Given a function to emit new lines for a given todo, write that update in -- place of the current todo lines updateTodoLinesInFile :: MonadIO m => (TodoEntry -> [Text]) -> TodoEntry -> m ()@@ -201,19 +207,19 @@ removeTodoFromCode = updateTodoLinesInFile (const []) setAbsolutePath :: ToodlesArgs -> IO ToodlesArgs-setAbsolutePath toodlesArgs = do- let pathOrDefault = if T.null . T.pack $ directory toodlesArgs+setAbsolutePath args = do+ let pathOrDefault = if T.null . T.pack $ directory args then "."- else directory toodlesArgs+ else directory args absolute <- normalise_path <$> absolute_path pathOrDefault- return $ toodlesArgs {directory = absolute}+ return $ args {directory = absolute} getFullSearchResults :: ToodlesState -> Bool -> IO TodoListResult getFullSearchResults (ToodlesState ref _) recompute = if recompute then do putStrLn "refreshing todo's"- userArgs <- cmdArgs argParser >>= setAbsolutePath+ userArgs <- toodlesArgs >>= setAbsolutePath sResults <- runFullSearch userArgs atomicModifyIORef' ref (const (sResults, sResults)) else putStrLn "cached read" >> readIORef ref@@ -224,11 +230,12 @@ configExists <- doesFileExist $ projectRoot ++ "/.toodles.yaml" config <- if configExists then Y.decodeFileEither (projectRoot ++ "/.toodles.yaml")- else return . Right $ ToodlesConfig []+ else return . Right $ ToodlesConfig [] [] when (isLeft config) $ putStrLn $ "[WARNING] Invalid .toodles.yaml: " ++ show config- allFiles <- getAllFiles (fromRight (ToodlesConfig []) config) projectRoot- let parsedTodos = concatMap runTodoParser allFiles+ let config' = fromRight (ToodlesConfig [] []) config+ allFiles <- getAllFiles config' projectRoot+ let parsedTodos = concatMap (runTodoParser $ userFlag userArgs ++ flags config') allFiles filteredTodos = filter (filterSearch (assignee_search userArgs)) parsedTodos resultList = limitSearch filteredTodos $ limit_results userArgs indexedResults = map (\(i, r) -> r {entryId = i}) $ zip [1 ..] resultList@@ -244,7 +251,7 @@ limitSearch todoList n = take n todoList getAllFiles :: ToodlesConfig -> FilePath -> IO [SourceFile]-getAllFiles (ToodlesConfig ignoredPaths) basePath =+getAllFiles (ToodlesConfig ignoredPaths _) basePath = E.catch (do putStrLn $ printf "Running toodles for path: %s" basePath files <- recurseDir SystemFS basePath
app/Types.hs view
@@ -1,12 +1,19 @@-{-# LANGUAGE DeriveAnyClass,- DeriveGeneric #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-} module Types where -import Data.Aeson (ToJSON, FromJSON)-import Data.IORef (IORef)-import Data.Text (Text)-import GHC.Generics (Generic)+import Data.Aeson (FromJSON, ToJSON, Value (String), parseJSON,+ toJSON)+import Data.Aeson.Types (typeMismatch)+import Data.Data+import Data.IORef (IORef)+import Data.String (IsString)+import Data.Text (Text)+import qualified Data.Text as T (unpack)+import GHC.Generics (Generic) data SourceFile = SourceFile { fullPath :: FilePath@@ -27,20 +34,24 @@ , sourceFile :: FilePath , lineNumber :: LineNumber , priority :: Maybe Integer+ , flag :: Flag , customAttributes :: [(Text, Text)] , tags :: [Text] , leadingText :: Text } | TodoBodyLine Text- deriving (Show, Generic, ToJSON)+ deriving (Show, Generic)+instance ToJSON TodoEntry data TodoListResult = TodoListResult { todos :: [TodoEntry] , message :: Text- } deriving (Show, Generic, ToJSON)+ } deriving (Show, Generic)+instance ToJSON TodoListResult newtype DeleteTodoRequest = DeleteTodoRequest { ids :: [Integer]- } deriving (Show, Generic, FromJSON)+ } deriving (Show, Generic)+instance FromJSON DeleteTodoRequest data EditTodoRequest = EditTodoRequest { editIds :: [Integer]@@ -48,4 +59,39 @@ , addTags :: [Text] , addKeyVals :: [(Text, Text)] , setPriority :: Maybe Integer- } deriving (Show, Generic, FromJSON)+ } deriving (Show, Generic)+instance FromJSON EditTodoRequest++data Flag = TODO | FIXME | XXX | UF UserFlag+ deriving (Generic)++newtype UserFlag = UserFlag Text+ deriving (Show, Eq, IsString, Data, Generic)++instance Show Flag where+ show TODO = "TODO"+ show FIXME = "FIXME"+ show XXX = "XXX"+ show (UF (UserFlag x)) = T.unpack x++instance ToJSON Flag where+ toJSON TODO = Data.Aeson.String "TODO"+ toJSON FIXME = Data.Aeson.String "FIXME"+ toJSON XXX = Data.Aeson.String "XXX"+ toJSON (UF (UserFlag x)) = Data.Aeson.String x++instance FromJSON Flag where+ parseJSON (Data.Aeson.String x) =+ case x of+ "TODO" -> pure TODO+ "FIXME" -> pure FIXME+ "XXX" -> pure XXX+ _ -> pure $ UF $ UserFlag x+ parseJSON invalid = typeMismatch "UserFlag" invalid++instance ToJSON UserFlag where+ toJSON (UserFlag x) = Data.Aeson.String x++instance FromJSON UserFlag where+ parseJSON (Data.Aeson.String x) = pure $ UserFlag x+ parseJSON invalid = typeMismatch "UserFlag" invalid
toodles.cabal view
@@ -1,7 +1,8 @@-cabal-version: >=1.10+cabal-version: 1.12 name: toodles-version: 0.1.0.16+version: 0.1.1 license: MIT+license-file: LICENSE copyright: 2018 Avi Press maintainer: mail@avi.press author: Avi Press@@ -13,16 +14,16 @@ category: Project Management build-type: Simple data-files:+ web/js/app.js+ web/js/jquery-3.3.1.min.js+ web/js/vue.js+ web/html/index.html web/css/bulma.min.css web/css/font-awesome.min.css web/css/toodles.css web/fonts/fontawesome-webfont.woff web/fonts/fontawesome-webfont.woff2- web/html/index.html web/img/favicon.png- web/js/app.js- web/js/jquery-3.3.1.min.js- web/js/vue.js extra-source-files: README.md @@ -41,7 +42,8 @@ Types Paths_toodles default-language: Haskell2010- ghc-options: -threaded -rtsopts -O3 -Wall -with-rtsopts=-N+ ghc-options: -Wall -Wcompat -threaded -rtsopts -O3 -Wall+ -with-rtsopts=-N build-depends: MissingH >=1.4.0.1 && <1.5, aeson ==1.3.1.1,
web/html/index.html view
@@ -124,16 +124,18 @@ <i class="fa fa-sort"></i> </span> </th>+ <th>Flag</th> <th class="todo-item-body">Body</th> <th>Assignee</th> <th>Tags</th> </tr> </thead>- <tr class="todo-item" v-for="todo in todos" v-if="todo.body.indexOf(todoSearch) !== -1 || (todo.assignee || '').indexOf(todoSearch) !== -1 || (todo.tags || []).toString().indexOf(todoSearch) !== -1" @change="updateTodo('top level')">+ <tr class="todo-item" v-for="todo in todos" v-if="todo.body.indexOf(todoSearch) !== -1 || (todo.assignee || '').indexOf(todoSearch) !== -1 || (todo.tags || []).toString().indexOf(todoSearch) !== -1 || (todo.flag || '').indexOf(todoSearch) !== -1" @change="updateTodo('top level')"> <td><input type="checkbox" v-model="todo.selected"></td> <td class="todo-item-priority" > <div v-show="todo.priority !== undefined">{{ todo.priority }}</div> </td>+ <td class="todo-item-flag" >{{ todo.flag }}</td> <td class='todo-item-body'> <div class="todo-source-link"><a :href="'/source_file/' + todo.id + '#line-' + todo.lineNumber" target="_blank">{{ todo.sourceFile }}:{{ todo.lineNumber}}</a></div> <div>{{ todo.body }}</div>
web/js/app.js view
@@ -33,18 +33,19 @@ this.todos = data.todos.map(t => { return { id: t.entryId,- assignee: t.assignee, body: t.body.join("\n"),- lineNumber: t.lineNumber,+ assignee: t.assignee, sourceFile: t.sourceFile,+ lineNumber: t.lineNumber, priority: t.priority,- tags: t.tags,+ flag: t.flag, customAttributes: t.customAttributes.reduce((acc, curr) => { console.log(acc, curr) acc[curr[0]] = curr[1] console.log(acc, curr) return acc }, {}),+ tags: t.tags, selected: false } })@@ -236,4 +237,3 @@ } }) })-