sarsi 0.0.4.0 → 0.0.5.1
raw patch · 27 files changed
+1089/−464 lines, 27 filesdep +ansi-terminaldep +msgpackdep −data-msgpackdep ~asyncdep ~basedep ~bytestringnew-component:exe:srs
Dependencies added: ansi-terminal, msgpack
Dependencies removed: data-msgpack
Dependency ranges changed: async, base, bytestring, containers, cryptonite, directory, fsnotify, machines, machines-binary, machines-io, machines-process, network, process, sarsi, stm, vector
Files
- exe/srs.hs +48/−0
- sarsi-hs/Main.hs +0/−14
- sarsi-nvim/Data/MessagePack/RPC.hs +5/−5
- sarsi-nvim/Main.hs +282/−47
- sarsi-nvim/NVIM/Client.hs +65/−19
- sarsi-nvim/NVIM/Command.hs +40/−0
- sarsi-nvim/NVIM/QuickFix.hs +39/−0
- sarsi-rs/Main.hs +0/−12
- sarsi-sbt/Main.hs +18/−18
- sarsi-vi/Main.hs +31/−30
- sarsi.cabal +51/−49
- sarsi/Main.hs +36/−14
- src/Codec/Sarsi.hs +12/−10
- src/Codec/Sarsi/Curses.hs +85/−0
- src/Codec/Sarsi/Rust.hs +19/−29
- src/Codec/Sarsi/SBT.hs +34/−38
- src/Codec/Sarsi/SBT/Machine.hs +23/−25
- src/Data/Attoparsec/Machine.hs +16/−13
- src/Data/Attoparsec/Text/Machine.hs +13/−6
- src/Rosetta.hs +60/−0
- src/Sarsi.hs +5/−7
- src/Sarsi/Consumer.hs +19/−17
- src/Sarsi/Processor.hs +52/−0
- src/Sarsi/Producer.hs +71/−41
- src/Sarsi/Tools/Pipe.hs +40/−0
- src/Sarsi/Tools/Shell.hs +0/−61
- src/Sarsi/Tools/Trace.hs +25/−9
+ exe/srs.hs view
@@ -0,0 +1,48 @@+module Main where++import Control.Concurrent.Async (async, wait)+import qualified Data.ByteString as ByteString+import qualified Data.List as List+import Data.Machine (Y (Z), auto, autoM, awaits, repeatedly, runT_, wye, yield, (<~))+import Sarsi.Processor (processAny)+import Sarsi.Tools.Pipe (pipeFrom)+import System.Environment (getArgs)+import System.Exit (exitWith)+import System.IO (stderr, stdout)+import System.IO.Machine (byLine, sinkHandleWith, sourceHandle, sourceHandleWith)+import System.Process++main :: IO ()+main = do+ args <- getArgs+ (dispatchErrRead, dispatchErrWrite) <- createPipe+ (dispatchOutRead, dispatchOutWrite) <- createPipe+ (Nothing, Just hout, Just herr, hprocess) <- createProcess $ cp (concat $ List.intersperse " " args)+ conveyorErr <- conveyorRun stderr herr dispatchErrWrite+ conveyorOut <- conveyorRun stdout hout dispatchOutWrite+ let sourceErr = sourceHandle byLine dispatchErrRead+ let sourceOut = sourceHandle byLine dispatchOutRead+ let process = processAny+ worker <- async $ pipeFrom "any" process $ (auto merge) <~ wye sourceErr sourceOut slurp+ wait conveyorErr+ wait conveyorOut+ wait worker+ ec <- waitForProcess hprocess+ exitWith ec+ where+ cp cmd =+ (proc "script" ["-qec", cmd, "/dev/null"])+ { delegate_ctlc = True,+ std_in = Inherit,+ std_out = CreatePipe,+ std_err = CreatePipe+ }+ conveyorRun hOutput hInput hDispatchWrite =+ async . runT_ $+ (sinkHandleWith ByteString.hPut hOutput) <~ autoM (\xs -> ByteString.hPut hDispatchWrite xs >> return xs)+ <~ (sourceHandleWith (\h -> ByteString.hGetNonBlocking h 1) hInput)+ merge (Right x) = x+ merge (Left x) = x+ slurp = repeatedly $ do+ res <- awaits Z+ yield res
− sarsi-hs/Main.hs
@@ -1,14 +0,0 @@-{-# LANGUAGE Rank2Types #-}-module Main where--import Codec.GHC.Log (messageParser)-import Codec.Sarsi.GHC (fromGHCLog)-import Data.Attoparsec.Text.Machine (streamParser)-import Data.Machine ((<~), asParts, auto)-import Sarsi.Tools.Shell (mainShell)--main :: IO ()-main = mainShell "haskell" (asParts <~ auto unpack <~ streamParser messageParser)- where- unpack (Right msg) = [fromGHCLog msg]- unpack (Left _) = []
sarsi-nvim/Data/MessagePack/RPC.hs view
@@ -1,28 +1,28 @@ module Data.MessagePack.RPC where import Data.Binary (Get, Put, get, getWord8, put)-import Data.Int (Int64) import Data.MessagePack (Object(..)) import Data.Text (Text) import qualified Data.MessagePack as MP+import qualified Data.Vector as Vector data Answer = Success Object | Error Object deriving Show data Request = Request- { requestMessageID :: Int64+ { requestMessageID :: Int , requestMethod :: Text , requestParams :: [Object] } deriving Show putRequest :: Request -> Put putRequest (Request msgID method params) =- MP.putArray id [MP.putInt 0, MP.putInt msgID, MP.putStr method, MP.putArray put params]+ MP.putArray id $ Vector.fromList [MP.putInt 0, MP.putInt msgID, MP.putStr method, MP.putArray put $ Vector.fromList params] data Message = Response- { responseMessageID :: Int64+ { responseMessageID :: Int , responseAnswer :: Answer } | Notification { notificationMethod :: Text@@ -45,5 +45,5 @@ 2 -> do method <- MP.getStr (ObjectArray params) <- get- return $ Notification method params+ return $ Notification method (Vector.toList params) _ -> fail "unsupported message type"
sarsi-nvim/Main.hs view
@@ -1,62 +1,297 @@ module Main where -import Codec.Sarsi (Event(..), Level(..), Location(..), Message(..))-import Data.Machine (ProcessT, (<~), asParts, final, scan, sinkPart_, runT)-import Data.MessagePack.Object (Object(..))-import NVIM.Client (Command(..), runCommand)-import Sarsi (getBroker, getTopic, title)+import Codec.Sarsi (Event (..), Level (..), Location (..), Message (..))+import Control.Concurrent.Async (async, cancel)+import Control.Concurrent.STM (atomically)+import Control.Concurrent.STM.TBQueue (newTBQueue, readTBQueue)+import Control.Concurrent.STM.TVar (TVar, modifyTVar', newTVar, readTVar, readTVarIO, stateTVar, writeTVar)+import Control.Monad (when)+import Data.Machine (ProcessT, asParts, auto, autoM, final, runT, runT_, scan, sinkPart_, (<~))+import Data.Machine.Fanout (fanout)+import Data.MessagePack (Object (..))+import qualified Data.MessagePack.RPC as RPC+import Data.Text (Text)+import qualified Data.Text as Text+import Data.Vector (Vector)+import qualified Data.Vector as Vector+import NVIM.Client (CommandQueue, ask', mkConnection, send)+import NVIM.Command (Command (..))+import NVIM.QuickFix (toQuickFix)+import Sarsi (Topic (..), getBroker, getTopic, title) import Sarsi.Consumer (consumeOrWait)-import System.IO (BufferMode(NoBuffering), hSetBuffering, stdin, stdout)-import System.IO.Machine (sinkIO)+import System.Environment (getArgs)+import System.Exit (ExitCode (..), exitWith)+import System.IO (Handle, IOMode (WriteMode))+import qualified System.IO as IO+import System.IO.Machine (sinkIO, sourceIO) -import qualified Data.Text as Text+data BuildStatus = Starting | Building | Done+ deriving (Show, Eq) +data PluginAction = Focus | Next | Previous+ deriving (Bounded, Show, Enum, Eq, Ord, Read)++pluginActions :: [PluginAction]+pluginActions = [minBound ..]++data PluginState = PluginState+ { buildStatus :: BuildStatus,+ buildErrors :: Vector (Location, [Text]),+ buildWarnings :: Vector (Location, [Text]),+ focus :: Maybe (Level, Int),+ buffer :: Object,+ window :: Maybe Object+ }+ deriving (Show)+ echo :: String -> Command-echo str = VimCommand [ObjectStr . Text.pack $ concat ["echo \"", str, "\""]]+echo str = NvimCommand [ObjectStr . Text.pack $ concat ["echo \"", str, "\""]] echom :: String -> Command-echom str = VimCommand [ObjectStr . Text.pack $ concat ["echom \"", title, ": ", str, "\""]]+echom str = NvimCommand [ObjectStr . Text.pack $ concat ["echom \"", title, ": ", str, "\""]] -setqflist :: String -> [Object] -> Command-setqflist action items = VimCallFunction (Text.pack "setqflist") [ObjectArray items, ObjectStr $ Text.pack action]+jumpTo :: Location -> [Command]+jumpTo loc =+ (\x -> NvimCommand [ObjectStr . Text.pack $ x])+ <$> [ concat ["drop +", show $ line loc, " ", Text.unpack $ filePath loc],+ concat ["call cursor(", show $ line loc, ", ", show $ column loc, ")"],+ "normal zz"+ ] -setqflistEmpty :: Command-setqflistEmpty = setqflist "r" []+openLogFile :: Topic -> IO Handle+openLogFile (Topic _ fp) = IO.openFile (concat [fp, "-nvim.log"]) WriteMode --- TODO Sanitize text description by escaping special characters-mkQuickFix :: Message -> Object-mkQuickFix (Message (Location fp col ln) lvl txts) = ObjectMap $- [ (ObjectStr $ Text.pack "filename", ObjectStr fp)- , (ObjectStr $ Text.pack "lnum", ObjectInt ln)- , (ObjectStr $ Text.pack "col", ObjectInt col)- , (ObjectStr $ Text.pack "type", ObjectStr . Text.pack $ tpe lvl)- , (ObjectStr $ Text.pack "text", ObjectStr $ Text.unlines txts) ]- where- tpe Error = "E"- tpe Warning = "W"+parseAction :: (Text, [Object]) -> PluginAction+parseAction (m, _) = read $ Text.unpack m -convert :: Int -> Event -> (Int, [Command])-convert _ e@(Start _) = (0, [echom $ show e])-convert i e@(Finish _ _) = (0, (echom $ show e) : (if i == 0 then [setqflistEmpty] else []))-convert i (Notify msg@(Message loc lvl _)) = (i + 1, xs) where- xs =- [ setqflist (if i == 0 then "r" else "a") [mkQuickFix msg]- , echo $ concat [show loc, " ", show lvl] ]+parseArgs :: [String] -> Either String Bool+parseArgs [] = Right False+parseArgs ["--log"] = Right True+parseArgs _ = Left "usage: [--log]" +pluginStateInit :: Object -> PluginState+pluginStateInit b = PluginState Done Vector.empty Vector.empty Nothing b Nothing++putLogLn :: Maybe Handle -> String -> IO ()+putLogLn Nothing _ = return ()+putLogLn (Just h) s = IO.hPutStrLn h s >> IO.hFlush h++registerActions :: [Command]+registerActions =+ (\x -> NvimCommand [ObjectStr . Text.pack $ concat ["command! Sarsi", x, " call rpcnotify(g:sarsi, '", x, "')"]])+ <$> show+ <$> pluginActions++update :: Monoid a => Maybe Handle -> CommandQueue -> TVar PluginState -> Event -> IO a+update h q s' e = do+ display e+ case e of+ (Start _) -> updateState (\s -> s {buildStatus = Starting})+ (Finish _ _) -> do+ emptyErrors <-+ atomically $+ stateTVar+ s'+ ( \s -> case buildStatus s of+ Building -> (Vector.null $ buildErrors s, s {buildStatus = Done})+ _ -> (True, s {buildStatus = Done, buildErrors = Vector.empty, buildWarnings = Vector.empty})+ )+ -- TODO Do auto-focus (only if users did not focus since Building)+ when emptyErrors $ windowClose q s'+ (Notify msg) ->+ updateState+ ( \s ->+ if buildStatus s /= Building+ then s {buildStatus = Building, focus = Nothing, buildErrors = Vector.empty, buildWarnings = Vector.empty}+ else s+ )+ >> updateMsg msg+ trace h+ return mempty+ where+ display (Start _) = nvim_ h q $ echom $ show e+ display (Finish _ _) = nvim_ h q $ echom $ show e+ display (Notify (Message loc lvl _)) = nvim_ h q $ echo $ concat [show loc, " ", show lvl]+ trace Nothing = return ()+ trace _ = do+ s <- readTVarIO s'+ putLogLn h $ show s+ updateMsg msg = atomically $ modifyTVar' s' (f msg)+ where+ f x s = g x+ where+ g (Message loc Error txts) = s {buildErrors = Vector.snoc es (loc, txts)}+ g (Message loc Warning txts) = s {buildWarnings = Vector.snoc ws (loc, txts)}+ (es, ws) = case buildStatus s of+ Starting -> (Vector.empty, Vector.empty)+ _ -> (buildErrors s, buildWarnings s)+ updateState f = atomically $ modifyTVar' s' f++-- TODO Wrap this in an appropriate transformer+nvim :: Maybe Handle -> CommandQueue -> Command -> IO (Maybe Object)+nvim hLog q cmd = do+ r <- ask' q cmd+ case r of+ RPC.Success a -> return $ Just a+ RPC.Error err -> do+ putLogLn hLog $ show err+ return Nothing++nvim_ :: Maybe Handle -> CommandQueue -> Command -> IO ()+nvim_ h q c = nvim h q c >> return ()++-- TODO NEW STUFF, move above+-- TODO Important: could they all be into STM? how to avoid unnecessary readTVarIO?+-- There must be a useful `Async + STM` atomic layer++bufferSetLines :: CommandQueue -> TVar PluginState -> [Text] -> IO ()+bufferSetLines q s' txts = do+ s <- readTVarIO s'+ let b = buffer s+ (RPC.Success _) <- ask' q $ NvimBufSetLines b 0 64 False txts+ return ()++windowClose :: CommandQueue -> TVar PluginState -> IO ()+windowClose q s' = do+ s <- readTVarIO s'+ case window s of+ Nothing -> return ()+ Just w -> do+ -- Tolerate failure if window was closed manually by user+ _ <- ask' q $ NvimWinClose w False+ atomically . modifyTVar' s' $ \x -> x {window = Nothing}+ return ()++bufferShow :: CommandQueue -> TVar PluginState -> Int -> IO ()+bufferShow q s' height = do+ windowClose q s'+ (RPC.Success (ObjectInt rows)) <- ask' q (NvimWinGetHeight $ ObjectInt 0)+ (RPC.Success (ObjectInt cols)) <- ask' q (NvimWinGetWidth $ ObjectInt 0)+ s <- readTVarIO s'+ (RPC.Success w) <- ask' q $ openWin (buffer s) rows cols+ atomically . modifyTVar' s' $ \x -> x {window = Just w}+ return ()+ where+ openWin b rows cols =+ NvimOpenWin+ b+ False+ ( ObjectMap $+ ( Vector.fromList+ [ (ObjectStr $ Text.pack "style", ObjectStr $ Text.pack "minimal"),+ (ObjectStr $ Text.pack "relative", ObjectStr $ Text.pack "win"),+ (ObjectStr $ Text.pack "row", ObjectInt $ rows - height),+ (ObjectStr $ Text.pack "col", ObjectInt 0),+ (ObjectStr $ Text.pack "width", ObjectInt cols),+ (ObjectStr $ Text.pack "height", ObjectInt height)+ ]+ )+ )++actionFocus :: Maybe Handle -> CommandQueue -> TVar PluginState -> Level -> Int -> IO ()+actionFocus hLog q s' lvl rank = do+ s <- readTVarIO s'+ let (loc, txts) = focusContent lvl rank s+ _ <- traverse (nvim_ hLog q) $ jumpTo loc+ bufferSetLines q s' txts+ bufferShow q s' $ length txts+ return ()++actionMove :: Maybe Handle -> CommandQueue -> TVar PluginState -> (PluginState -> PluginState) -> IO ()+actionMove hLog q s' f = do+ fcs <-+ atomically $ do+ s <- readTVar s'+ let s'' = f s+ writeTVar s' s''+ return $ focus s''+ case fcs of+ Nothing -> return ()+ Just (lvl, rank) -> actionFocus hLog q s' lvl rank++focusContent :: Level -> Int -> PluginState -> (Location, [Text])+focusContent lvl rank s = Vector.unsafeIndex xs rank+ where+ xs = case lvl of+ Warning -> buildWarnings s+ Error -> buildErrors s++focusDefault :: PluginState -> Maybe (Level, Int)+focusDefault s = select (Vector.null $ buildErrors s) (Vector.null $ buildWarnings s)+ where+ select False _ = Just (Error, 0)+ select True False = Just (Warning, 0)+ select True True = Nothing++focusMove :: Int -> PluginState -> PluginState+focusMove i s =+ case focus s of+ Nothing -> s {focus = focusDefault s}+ Just (lvl, rank) -> s {focus = Just $ f lvl (rank + i)}+ where+ f lvl rank | rank < 0 = f (toggle lvl) ((Vector.length $ select lvl) + rank)+ f lvl rank | rank >= (Vector.length $ select lvl) = f (toggle lvl) (rank - (Vector.length $ select lvl))+ f lvl rank = (lvl, rank)+ toggle lvl | Vector.null $ select (toggle' lvl) = lvl+ toggle lvl = toggle' lvl+ toggle' Warning = Error+ toggle' Error = Warning+ select Warning = buildWarnings s+ select Error = buildErrors s++-- TODO How to make it shudown gracefully? currently it's probably killed by nvim while blocking in `consumerOrWait` main :: IO () main = do- hSetBuffering stdin NoBuffering- hSetBuffering stdout NoBuffering- b <- getBroker- t <- getTopic b "."- consumeOrWait t consumer- where- consumer Nothing src = consumer (Just 0) src- consumer (Just i) src = do- i' <- runT $ final <~ sinkPart_ id (sinkIO publish <~ asParts) <~ converter i <~ src- return (Left $ head i')- converter :: Int -> ProcessT IO Event (Int, [Command])- converter i = scan f (i, []) where f (first, _) event = convert first event- publish cmd = do- _ <- runCommand cmd- return ()+ args <- getArgs+ case parseArgs args of+ Left err -> do+ putStrLn err+ exitWith $ ExitFailure 1+ Right logging -> do+ IO.hSetBuffering IO.stdin IO.NoBuffering+ IO.hSetBuffering IO.stdout IO.NoBuffering+ b <- getBroker+ t <- getTopic b "."+ hLog <- if logging then Just <$> (openLogFile t) else return Nothing+ qCmds <- atomically $ newTBQueue 8+ qNotifs <- atomically $ newTBQueue 8+ connClose <- mkConnection IO.stdin IO.stdout qCmds qNotifs (errHandler hLog)+ (Just buf) <- nvim hLog qCmds $ NvimCreateBuf False True+ state <- atomically $ newTVar $ pluginStateInit buf+ notifier <-+ async . runT_ $+ autoM (notify hLog qCmds state) <~ (auto parseAction) <~ (sourceIO . atomically $ readTBQueue qNotifs)+ _ <- traverse (nvim_ hLog qCmds) registerActions+ putLogLn hLog "ready"+ _ <- consumeOrWait t (consumer hLog state qCmds)+ cancel notifier+ connClose+ _ <- traverse IO.hClose hLog+ return ()+ where+ errHandler hLog err = do+ putLogLn hLog $ show err+ notify hLog q s' Focus = do+ s <- readTVarIO s'+ case focus s of+ Nothing ->+ case focusDefault s of+ Nothing -> nvim_ hLog q $ echom "nothing to fix"+ Just (lvl, rank) -> do+ atomically . modifyTVar' s' $ \x -> x {focus = Just (lvl, rank)}+ actionFocus hLog q s' lvl rank+ Just (lvl, rank) -> actionFocus hLog q s' lvl rank+ notify hLog q s' Next = actionMove hLog q s' (focusMove 1)+ notify hLog q s' Previous = actionMove hLog q s' (focusMove (-1))+ consumer h s q Nothing src = consumer h s q (Just 0) src+ consumer h s q (Just i) src = do+ i' <- runT $ final <~ asParts <~ fanout [quickFixes, pluginUpdate] <~ src+ return (Left $ head i')+ where+ quickFixes = auto (\x -> [x]) <~ sinkPart_ id (sinkIO (send q) <~ asParts) <~ toQuickFixes i+ pluginUpdate = autoM (update h q s)++ toQuickFixes :: Int -> ProcessT IO Event (Int, [Command])+ toQuickFixes acc = scan f (acc, [])+ where+ f (i, _) event = toQuickFix i event
sarsi-nvim/NVIM/Client.hs view
@@ -1,35 +1,81 @@ module NVIM.Client where --- | A primitive NVIM RPC synchronous client.--import Data.Binary.Machine (streamGet)+import Control.Concurrent.Async (Async, async, wait)+import Control.Concurrent.STM (STM, atomically)+import Control.Concurrent.STM.TBQueue (TBQueue, readTBQueue, writeTBQueue)+import Control.Concurrent.STM.TMVar (newEmptyTMVarIO, putTMVar, takeTMVar)+import Control.Concurrent.STM.TVar (modifyTVar', newTVar, stateTVar)+import Data.Binary.Machine (DecodingError (..), processPut, streamGet) import Data.Binary.Put (runPut) import Data.ByteString (hGetSome, hPut) import Data.ByteString.Lazy (toStrict)-import Data.Machine ((<~), asParts, auto, final, run, source)-import Data.MessagePack.Object (Object(..))-import Data.MessagePack.RPC (Answer(..), Request(..), Message(..), getMessage, putRequest)+import Data.Machine (autoM, final, run, runT_, source, (<~))+import qualified Data.Map as Map+import Data.MessagePack (Object (..))+import Data.MessagePack.RPC (Answer (..), Message (..), getMessage, putRequest) import Data.Text (Text)-import System.IO (Handle, stdin, stdout) import qualified Data.Text as Text+import NVIM.Command (Command (..), mkRequest)+import System.IO (Handle, stdin, stdout)+import System.IO.Machine (byChunk, sinkHandle, sourceHandle, sourceIO) -data Command = VimCommand [Object] | VimCallFunction Text [Object]+-- TODO Extract a generic asynchronous RPC client+-- need to write a proper scheduler first to support timeout on transaction+-- type JobQueue a = TBQueue (IO a, Maybe a -> STM (), UTCTime)+-- type CommandQueue = TBQueue (Command, Maybe Answer -> STM (), Duration) +-- Asynchronous client+type CommandQueue = TBQueue (Command, Answer -> STM ())++ask :: CommandQueue -> Command -> IO (Async Answer)+ask q cmd = do+ answer <- newEmptyTMVarIO+ atomically $ writeTBQueue q (cmd, putTMVar answer)+ async $ atomically $ takeTMVar answer++ask' :: CommandQueue -> Command -> IO Answer+ask' q c = wait =<< ask q c++send :: CommandQueue -> Command -> IO ()+send q c = atomically $ writeTBQueue q (c, \_ -> return ())++mkConnection :: Handle -> Handle -> CommandQueue -> TBQueue (Text, [Object]) -> (DecodingError -> IO ()) -> IO (IO ())+mkConnection hIn hOut qCommands qNotifs errHandler = do+ nonce <- atomically $ newTVar 0+ runnings <- atomically $ newTVar Map.empty+ sender <- async . runT_ $ outbound nonce qCommands runnings+ receiver <- async . runT_ $ inbound runnings+ return $ wait sender >> wait receiver+ where+ outbound nonce q rs =+ sinkHandle byChunk hOut <~ processPut putRequest+ <~ autoM (publish rs nonce)+ <~ (sourceIO . atomically $ readTBQueue q)+ inbound rs = autoM (dispatch rs) <~ streamGet getMessage <~ sourceHandle byChunk hIn+ dispatch rs (Right (Response msgId a)) = do+ cb <- atomically $ stateTVar rs (\rs' -> (Map.lookup msgId rs', Map.delete msgId rs'))+ case cb of+ Just cb' -> atomically $ cb' a+ Nothing -> return ()+ dispatch _ (Right ((Notification m ps))) = do+ atomically $ writeTBQueue qNotifs (m, ps)+ dispatch _ (Left err) = errHandler err+ publish rs nonce (cmd, callback) = do+ msgId <- atomically $ stateTVar nonce (\n -> (n, n + 1))+ atomically $ modifyTVar' rs (\rs' -> Map.insert msgId callback rs')+ return $ mkRequest msgId cmd++-- Helpers to run a command synchronously runCommand :: Command -> IO Answer runCommand = runCommandWith stdin stdout runCommandWith :: Handle -> Handle -> Command -> IO Answer runCommandWith hIn hOut cmd = do- hPut hOut $ toStrict $ runPut $ putRequest $ mkRequest cmd+ hPut hOut $ toStrict $ runPut $ putRequest $ mkRequest 0 cmd xs <- hGetSome hIn 1024- let ys = run $ final <~ asParts <~ auto unpack <~ streamGet getMessage <~ source [xs]+ let ys = run $ final <~ streamGet getMessage <~ source [xs] return $ mkAnswer $ ys- where- mkAnswer [a] = a- mkAnswer _ = Error $ ObjectStr $ Text.pack "No RPC answer received."- mkRequest (VimCommand xs) =- Request 0 (Text.pack "vim_command") xs- mkRequest (VimCallFunction m xs) =- Request 0 (Text.pack "vim_call_function") [ ObjectStr m, ObjectArray xs ]- unpack (Right (Response _ a)) = [a]- unpack _ = []+ where+ mkAnswer [(Right (Response _ a))] = a+ mkAnswer [(Left (DecodingError _ err))] = Error . ObjectStr $ Text.pack err+ mkAnswer _ = Error $ ObjectStr $ Text.pack "No RPC answer received."
+ sarsi-nvim/NVIM/Command.hs view
@@ -0,0 +1,40 @@+module NVIM.Command where++import Data.MessagePack (Object (..))+import Data.MessagePack.RPC (Request (..))+import Data.Text (Text)+import qualified Data.Text as Text+import qualified Data.Vector as Vector++-- TODO Support special types (with newtype over Object) for Buffer, Window, Tabpage (EXT 0, 1, 2)+-- see https://neovim.io/doc/user/api.html+data Command+ = NvimBufSetLines Object Int Int Bool [Text]+ | NvimCommand [Object]+ | NvimCallFunction Text [Object]+ | NvimCreateBuf Bool Bool -- {listed} {scratch}+ | NvimOpenWin Object Bool Object+ | NvimWinClose Object Bool+ | NvimWinGetBuf Object+ | NvimWinGetHeight Object+ | NvimWinGetWidth Object++mkRequest :: Int -> Command -> Request+mkRequest msgId (NvimBufSetLines b s e strict xs) =+ Request msgId (Text.pack "nvim_buf_set_lines") [b, ObjectInt s, ObjectInt e, ObjectBool strict, ObjectArray (Vector.fromList (ObjectStr <$> xs))]+mkRequest msgId (NvimCommand xs) =+ Request msgId (Text.pack "nvim_command") xs+mkRequest msgId (NvimCallFunction m xs) =+ Request msgId (Text.pack "nvim_call_function") [ObjectStr m, ObjectArray (Vector.fromList xs)]+mkRequest msgId (NvimCreateBuf l s) =+ Request msgId (Text.pack "nvim_create_buf") [ObjectBool l, ObjectBool s]+mkRequest msgId (NvimOpenWin b e cfg) =+ Request msgId (Text.pack "nvim_open_win") [b, ObjectBool e, cfg]+mkRequest msgId (NvimWinClose w force) =+ Request msgId (Text.pack "nvim_win_close") [w, ObjectBool force]+mkRequest msgId (NvimWinGetBuf w) =+ Request msgId (Text.pack "nvim_win_get_buf") [w]+mkRequest msgId (NvimWinGetHeight w) =+ Request msgId (Text.pack "nvim_win_get_height") [w]+mkRequest msgId (NvimWinGetWidth w) =+ Request msgId (Text.pack "nvim_win_get_width") [w]
+ sarsi-nvim/NVIM/QuickFix.hs view
@@ -0,0 +1,39 @@+module NVIM.QuickFix where++import Codec.Sarsi (Event (..), Level (..), Location (..), Message (..))+import Data.MessagePack (Object (..))+import qualified Data.Text as Text+import qualified Data.Vector as Vector+import NVIM.Command (Command (NvimCallFunction))++toQuickFix :: Int -> Event -> (Int, [Command])+toQuickFix _ (Start _) = (0, [])+toQuickFix i (Finish _ _) = (0, (if i == 0 then [setqflistEmpty] else []))+toQuickFix i (Notify msg@(Message _ _ _)) = (i + 1, [setqflist (if i == 0 then "r" else "a") [mkQuickFix msg]])++-- TODO Sanitize text description by escaping special characters+mkQuickFix :: Message -> Object+mkQuickFix (Message (Location fp col ln) lvl txts) =+ ObjectMap $+ ( Vector.fromList+ [ (ObjectStr $ Text.pack "filename", ObjectStr fp),+ (ObjectStr $ Text.pack "lnum", ObjectInt ln),+ (ObjectStr $ Text.pack "col", ObjectInt col),+ (ObjectStr $ Text.pack "type", ObjectStr . Text.pack $ tpe lvl),+ (ObjectStr $ Text.pack "text", ObjectStr $ Text.unlines txts)+ ]+ )+ where+ tpe Error = "E"+ tpe Warning = "W"++setqflist :: String -> [Object] -> Command+setqflist action items =+ NvimCallFunction+ (Text.pack "setqflist")+ [ ObjectArray (Vector.fromList items),+ ObjectStr $ Text.pack action+ ]++setqflistEmpty :: Command+setqflistEmpty = setqflist "r" []
− sarsi-rs/Main.hs
@@ -1,12 +0,0 @@-module Main where--import Codec.Sarsi.Rust (messageParser)-import Data.Attoparsec.Text.Machine (streamParser)-import Data.Machine ((<~), asParts, auto)-import Sarsi.Tools.Shell (mainShell)--main :: IO ()-main = mainShell "rust" (asParts <~ auto unpack <~ streamParser messageParser)- where- unpack (Right msg) = [msg]- unpack (Left _) = []
sarsi-sbt/Main.hs view
@@ -1,45 +1,45 @@ {-# LANGUAGE Rank2Types #-}+ module Main where import Codec.Sarsi (Event) import Codec.Sarsi.SBT.Machine (eventProcess)-import Data.Machine (ProcessT, (<~), autoM, runT_)+import qualified Data.List as List+import Data.Machine (ProcessT, autoM, runT_, (<~))+import qualified Data.Text.IO as TextIO import Sarsi (getBroker, getTopic)+import qualified Sarsi as Sarsi import Sarsi.Producer (produce) import System.Environment (getArgs) import System.Exit (ExitCode, exitWith)-import System.Process (StdStream(..), shell, std_in, std_out)-import System.Process.Machine (ProcessMachines, callProcessMachines)-import System.IO (BufferMode(NoBuffering), hSetBuffering, stdin, stdout)+import System.IO (BufferMode (NoBuffering), hSetBuffering, stdin, stdout) import System.IO.Machine (byChunk)--import qualified Data.List as List-import qualified Data.Text.IO as TextIO-import qualified Sarsi as Sarsi+import System.Process (StdStream (..), shell, std_in, std_out)+import System.Process.Machine (ProcessMachines, callProcessMachines) title :: String title = concat [Sarsi.title, "-sbt"] -- TODO Contribute to machines-process mStdOut_ :: ProcessT IO a b -> ProcessMachines a a0 k0 -> IO ()-mStdOut_ mp (_, Just stdOut, _) = runT_ $ mp <~ stdOut-mStdOut_ _ _ = return ()+mStdOut_ mp (_, Just stdOut, _) = runT_ $ mp <~ stdOut+mStdOut_ _ _ = return () producer :: String -> ProcessT IO Event Event -> IO (ExitCode) producer cmd sink = do (ec, _) <- callProcessMachines byChunk createProc (mStdOut_ pipeline) return ec- where- pipeline = sink <~ eventProcess <~ echoText stdout- echoText h = autoM $ (\txt -> TextIO.hPutStr h txt >> return txt)- createProc = (shell cmd) { std_in = Inherit, std_out = CreatePipe }+ where+ pipeline = sink <~ eventProcess <~ echoText stdout+ echoText h = autoM $ (\txt -> TextIO.hPutStr h txt >> return txt)+ createProc = (shell cmd) {std_in = Inherit, std_out = CreatePipe} main :: IO () main = do hSetBuffering stdin NoBuffering hSetBuffering stdout NoBuffering- args <- getArgs- b <- getBroker- t <- getTopic b "."- ec <- produce t $ producer $ concat $ List.intersperse " " ("sbt":args)+ args <- getArgs+ b <- getBroker+ t <- getTopic b "."+ ec <- produce t $ producer $ concat $ List.intersperse " " ("sbt" : "-Dsbt.color=always" : args) exitWith ec
sarsi-vi/Main.hs view
@@ -1,19 +1,18 @@ module Main where -import Data.Machine (ProcessT, (<~), asParts, final, scan, sinkPart_, runT)-import Codec.Sarsi (Event(..), Message(..), Level(..), Location(..))+import Codec.Sarsi (Event (..), Level (..), Location (..), Message (..)) import Control.Concurrent.MVar (MVar, newMVar, readMVar, swapMVar)+import qualified Data.List as List+import Data.Machine (ProcessT, asParts, final, runT, scan, sinkPart_, (<~)) import Data.Text (Text, pack)+import qualified Data.Text as Text import Data.Text.IO (hPutStrLn)-import Sarsi (Topic(..), getBroker, getTopic)+import Sarsi (Topic (..), getBroker, getTopic)+import qualified Sarsi as Sarsi import Sarsi.Consumer (consumeOrWait)-import System.IO (Handle, IOMode(WriteMode), hClose, hFlush, openFile)+import System.IO (Handle, IOMode (WriteMode), hClose, hFlush, openFile) import System.IO.Machine (sinkIO) -import qualified Data.List as List-import qualified Data.Text as Text-import qualified Sarsi as Sarsi- -- TODO Remove the MVar by impl. scanM ((a -> b -> m a) -> a -> ProcessT m (k b) a) in machines title :: String@@ -23,47 +22,49 @@ toVi :: Message -> Text toVi (Message (Location fp col ln) lvl txts) = Text.concat [header, pack " ", body] where- header = Text.concat $ List.intersperse (pack ":") [fp, pack $ show ln, pack $ show col, tpe lvl]- body = Text.concat $ List.intersperse (pack " ") txts+ header = Text.concat $ List.intersperse (pack ":") [fp, pack $ show ln, pack $ show col, tpe lvl]+ body = Text.concat $ List.intersperse (pack " ") txts tpe Error = pack "e" tpe Warning = pack "w" -data Action = Append | Replace deriving Show-data LogEvent = LogEvent Action Text deriving Show+data Action = Append | Replace deriving (Show) -data Command = Throw LogEvent | Echo String+data LogEvent = LogEvent Action Text deriving (Show) +data Command = Throw LogEvent | Echo String deriving (Show)+ convert :: Bool -> Event -> (Bool, [Command])-convert first (Notify msg@(Message loc lvl _)) = (False, [Throw e, Echo $ concat [show loc, " ", show lvl]])+convert first (Notify msg@(Message loc lvl _)) = (False, [Throw e, Echo $ concat [show loc, " ", show lvl]]) where e = LogEvent mode $ toVi msg mode = if first then Replace else Append-convert _ e = (True, [Echo $show e])+convert _ e = (True, [Echo $show e]) converter :: Bool -> ProcessT IO Event (Bool, [Command]) converter first = scan f (first, []) where f (first', _) event = convert first' event dump :: FilePath -> Maybe Handle -> Command -> IO (Maybe Handle)-dump _ h (Echo txt) = (putStrLn $ concat [title, ": ", txt]) >> return h-dump fp Nothing e = openFile fp WriteMode >>= \h -> dump fp (Just h) e-dump _ (Just h) (Throw (LogEvent Append txt)) = fmap (const $ Just h) $ (hPutStrLn h txt >> hFlush h)-dump fp (Just h) (Throw (LogEvent Replace txt)) = hClose h >> dump fp Nothing (Throw $ LogEvent Append txt)+dump _ h (Echo txt) = (putStrLn $ concat [title, ": ", txt]) >> return h+dump fp Nothing e = openFile fp WriteMode >>= \h -> dump fp (Just h) e+dump _ (Just h) (Throw (LogEvent Append txt)) = fmap (const $ Just h) $ (hPutStrLn h txt >> hFlush h)+dump fp (Just h) (Throw (LogEvent Replace txt)) = hClose h >> dump fp Nothing (Throw $ LogEvent Append txt) writer :: FilePath -> MVar (Maybe Handle) -> Command -> IO () writer tp var c = do- h <- readMVar var- h' <- dump fp h c- _ <- swapMVar var h'+ h <- readMVar var+ h' <- dump fp h c+ _ <- swapMVar var h' return ()- where fp = tp ++ ".vi"+ where+ fp = tp ++ ".vi" main :: IO () main = do- b <- getBroker- t <- getTopic b "."- var <- newMVar Nothing+ b <- getBroker+ t <- getTopic b "."+ var <- newMVar Nothing consumeOrWait t $ consumer t var- where- consumer (Topic _ tp) var first src = do- res <- runT $ final <~ sinkPart_ id ((sinkIO $ writer tp var) <~ asParts) <~ converter (maybe True id first) <~ src- return $ Left $ head res+ where+ consumer (Topic _ tp) var first src = do+ res <- runT $ final <~ sinkPart_ id ((sinkIO $ writer tp var) <~ asParts) <~ converter (maybe True id first) <~ src+ return $ Left $ head res
sarsi.cabal view
@@ -1,10 +1,8 @@ name: sarsi-version: 0.0.4.0+version: 0.0.5.1 synopsis: A universal quickfix toolkit and his protocol.- description: Usage overview can be found in the <http://github.com/aloiscochard/sarsi#sarsi README>.- homepage: http://github.com/aloiscochard/sarsi license: Apache-2.0 license-file: LICENSE@@ -20,64 +18,60 @@ ghc-options: -Wall exposed-modules: Codec.Sarsi+ Codec.Sarsi.Curses Codec.Sarsi.GHC+ -- TODO Extract in rosetta Codec.Sarsi.Rust Codec.Sarsi.SBT Codec.Sarsi.SBT.Machine+ Rosetta Sarsi Sarsi.Consumer+ Sarsi.Processor Sarsi.Producer- Sarsi.Tools.Shell+ Sarsi.Tools.Pipe Sarsi.Tools.Trace- -- TODO Extract in a `codec-ghc-log` module+ -- TODO Extract in a `codec-ghc-log` module or in rosetta Codec.GHC.Log -- TODO Extract in a `machines-attoparsec` module Data.Attoparsec.Machine Data.Attoparsec.Text.Machine build-depends: base >= 4.6.0.1 && < 5- , async >= 2.1 && < 2.2+ , ansi-terminal >= 0.10 && < 0.12+ , async >= 2.1 && < 2.3 , attoparsec >= 0.12 && < 0.14 , binary >= 0.7 && < 0.9 , bytestring >= 0.10 && < 0.11- , containers >= 0.5 && < 0.6- , cryptonite >= 0.10 && < 0.21- , data-msgpack >= 0.0.3 && < 0.1- , directory >= 1.2 && < 1.3+ , containers >= 0.5 && < 0.7+ , cryptonite >= 0.10 && < 0.29+ , msgpack >= 1.0 && < 1.1+ , directory >= 1.2 && < 1.4 , filepath >= 1.4 && < 1.5- , fsnotify >= 0.2 && < 0.3- , machines >= 0.6 && < 0.7- , machines-binary >= 0.3.0.2 && < 0.4- , machines-process >= 0.2.0.6 && < 0.3- , machines-io >= 0.2.0.12 && < 0.3- , network >= 2.6 && < 2.7- , process >= 1.1 && < 1.5- , stm >= 2.4 && < 2.5+ , fsnotify >= 0.2 && < 0.4+ , machines >= 0.7 && < 0.8+ , machines-binary >= 7.0 && < 7.1+ , machines-process >= 7.0.0.2 && < 7.1+ , machines-io >= 7.0 && < 7.1+ , network >= 3.1 && < 3.2+ , process >= 1.1 && < 1.7+ , stm >= 2.4 && < 2.6 , text >= 1.2 && < 1.3- , vector >= 0.10 && < 0.12+ , vector >= 0.10 && < 0.13 executable sarsi main-is: Main.hs other-modules: Paths_sarsi- build-depends: + build-depends: base+ , sarsi , Cabal- , sarsi == 0.0.4.0+ , containers hs-source-dirs: sarsi ghc-options: -Wall -threaded default-language: Haskell2010 -executable sarsi-hs- main-is: Main.hs- build-depends: - base- , sarsi == 0.0.4.0- , machines- hs-source-dirs: sarsi-hs- ghc-options: -Wall -dynamic -threaded- default-language: Haskell2010- executable sarsi-nvim main-is: Main.hs other-modules:@@ -86,10 +80,12 @@ Data.MessagePack.RPC -- TODO Extract in a lightweight nvim client. NVIM.Client- -- NVIM.Info- build-depends: + NVIM.Command+ NVIM.QuickFix+ build-depends: base- , sarsi == 0.0.4.0+ , sarsi+ , async , machines , binary , bytestring@@ -97,32 +93,23 @@ , machines , machines-binary , machines-io- , data-msgpack+ , msgpack , network , process , text+ , stm , vector , unordered-containers >= 0.2 && < 0.3 hs-source-dirs: sarsi-nvim ghc-options: -Wall -dynamic -threaded default-language: Haskell2010 -executable sarsi-rs- main-is: Main.hs- build-depends: - base- , sarsi == 0.0.4.0- , machines- hs-source-dirs: sarsi-rs- ghc-options: -Wall -dynamic -threaded- default-language: Haskell2010- executable sarsi-sbt main-is: Main.hs other-modules:- build-depends: + build-depends: base- , sarsi == 0.0.4.0+ , sarsi , machines , machines-io , machines-process@@ -134,9 +121,9 @@ executable sarsi-vi main-is: Main.hs- build-depends: + build-depends: base- , sarsi == 0.0.4.0+ , sarsi , directory , filepath , machines@@ -144,5 +131,20 @@ , text , vector hs-source-dirs: sarsi-vi+ ghc-options: -Wall -dynamic -threaded+ default-language: Haskell2010++executable srs+ main-is: srs.hs+ build-depends:+ base+ , sarsi+ , async+ , bytestring+ , machines+ , machines-io+ , machines-process+ , process+ hs-source-dirs: exe ghc-options: -Wall -dynamic -threaded default-language: Haskell2010
sarsi/Main.hs view
@@ -1,20 +1,42 @@ module Main where -import Distribution.Text+import qualified Data.List as List+import qualified Data.Set as Set+import Data.Version (showVersion)+import Paths_sarsi (version)+import qualified Rosetta as Rosetta import Sarsi (getBroker, getSockAddr, getTopic, title)-import Sarsi.Tools.Trace (traceHS, traceRS, traceSBT)-import System.IO (stdin)+import Sarsi.Processor (languageProcess, processAll, processAny)+import Sarsi.Tools.Pipe (pipe)+import Sarsi.Tools.Trace (traceCleanCurses, traceHS, traceRS, traceSBT, traceSBTCurses) import System.Environment (getArgs)--import Paths_sarsi (version)+import System.IO (stdin) main :: IO ()-main = getArgs >>= run where- run ["--trace-hs"] = traceHS stdin- run ["--trace-rs"] = traceRS stdin- run ["--trace-sbt"] = traceSBT stdin- run ["--version"] = putStrLn $ concat [title, "-", display version]- run _ = do- b <- getBroker- t <- getTopic b "."- print $ getSockAddr t+main = getArgs >>= run+ where+ run ["--trace", "clean-curses"] = traceCleanCurses stdin+ run ["--trace", "hs"] = traceHS stdin+ run ["--trace", "rs"] = traceRS stdin+ run ["--trace", "sbt"] = traceSBT stdin+ run ["--trace", "sbt-curses"] = traceSBTCurses stdin+ run ["--topic"] = do+ b <- getBroker+ t <- getTopic b "."+ print $ getSockAddr t+ run ["--version"] = putStrLn $ concat [title, "-", showVersion version]+ run [] = pipe "any" processAny+ run exts = case Set.fromList <$> traverse parseExt exts of+ Right languageTags -> do+ ps <- traverse fetchProcess $ Set.toList languageTags+ pipe (concat $ List.intersperse "+" (Rosetta.languageLabel <$> (Set.toList languageTags))) (processAll $ ps >>= id)+ Left err -> putStrLn $ concat [title, ": ", err]+ fetchProcess lt = case languageProcess lt of+ Just process -> return [process]+ Nothing -> do+ putStrLn $ concat [title, ": ", "unsupported language '", Rosetta.languageLabel lt, "'"]+ return []+ parseExt ext =+ case Rosetta.fromExtension ext of+ Just lt -> Right lt+ Nothing -> Left (concat ["invalid extension '", ext, "'."])
src/Codec/Sarsi.hs view
@@ -1,17 +1,16 @@ module Codec.Sarsi where -import Data.Int (Int64)-import Data.Text (Text, unpack) import Data.Binary (Get, Put)- import qualified Data.MessagePack.Get as Get import qualified Data.MessagePack.Put as Put+import Data.Text (Text, unpack) import qualified Data.Text as Text+import qualified Data.Vector as Vector data Event- = Start { label :: Text }- | Finish { errors :: Int64, warnings :: Int64 }- | Notify { message :: Message }+ = Start {label :: Text}+ | Finish {errors :: Int, warnings :: Int}+ | Notify {message :: Message} instance Show Event where show (Start lbl) = concat ["starting ", unpack lbl, " build"]@@ -37,17 +36,21 @@ data Message = Message Location Level [Text] +-- TODO Remove me+messageTest :: Message+messageTest = Message (Location {filePath = Text.pack "foo", column = 42, line = 42}) Error []+ instance Show Message where show (Message loc lvl txts) = (concat [show loc, " ", show lvl, "\n"]) ++ (unlines $ Text.unpack <$> txts) getMessage :: Get Message-getMessage = Message <$> getLocation <*> getLevel <*> Get.getArray Get.getStr+getMessage = Message <$> getLocation <*> getLevel <*> (Vector.toList <$> Get.getArray Get.getStr) putMessage :: Message -> Put-putMessage (Message loc lvl txt) = putLocation loc *> putLevel lvl *> Put.putArray Put.putStr txt+putMessage (Message loc lvl txt) = putLocation loc *> putLevel lvl *> Put.putArray Put.putStr (Vector.fromList txt) -data Location = Location { filePath :: Text, column :: Int64, line :: Int64 }+data Location = Location {filePath :: Text, column :: Int, line :: Int} instance Show Location where show (Location fp c l) = concat [Text.unpack fp, "@", show l, ":", show c]@@ -66,4 +69,3 @@ putLevel :: Level -> Put putLevel = Put.putInt . fromIntegral . fromEnum-
+ src/Codec/Sarsi/Curses.hs view
@@ -0,0 +1,85 @@+{-# LANGUAGE OverloadedStrings #-}++module Codec.Sarsi.Curses where++import Data.Attoparsec.Text+import qualified Data.Attoparsec.Text as AttoText+import Data.Maybe (fromMaybe)+import Data.Text (Text)+import qualified Data.Text as Text++-- note: expect a line that does NOT ends with a LF+cleanLine :: Text -> Text+cleanLine txt | Text.null txt = txt+cleanLine txt | Text.last txt == '\r' = fromMaybe Text.empty $ (f . fst) <$> Text.unsnoc txt+ where+ f x = case Text.breakOnAll "\r" x of+ [] -> x+ xs -> Text.tail $ (snd . last) xs+cleanLine txt = txt++-- Note: this parser remove CSI codes and do a best effort+-- at removing "clear line" instructions while keeping+-- all information exposed without any mangling.+cleaningCurses :: Parser Text+cleaningCurses = choice [multiples, single, none]+ where+ multiples = do+ before <- ln+ middle <- choice [silenceClearLines, silenceCSI]+ after <- choice [multiples, single]+ return $ Text.concat [before, "\n", middle, after]+ where+ ln = do+ before <- AttoText.takeWhile (breakAt . fromEnum)+ after <- choice [lineFinish, lineContinue]+ return (Text.concat [before, after])+ where+ breakAt 10 = False -- LF+ breakAt 27 = False -- ESC+ breakAt _ = True+ lineFinish = char '\n' >> (return $ Text.pack "\n")+ lineContinue = csi >> ln+ silenceClearLines = do+ _ <- AttoText.takeWhile (not . isEsc . fromEnum)+ _ <- choice [AttoText.many1 (cl <* "\n"), AttoText.many1 cl]+ return Text.empty+ single = do+ befores <- many1 $ choice [clearLine, silenceCSI]+ str <- AttoText.many1 anyChar+ _ <- endOfInput+ return (Text.concat [Text.concat befores, Text.pack str])+ where+ clearLine = do+ _ <- AttoText.takeWhile (not . isEsc . fromEnum)+ _ <- AttoText.many1 cl+ return Text.empty+ none = do+ ln <- (AttoText.takeWhile $ \w -> w /= '\n')+ _ <- "\n"+ return $ Text.concat [ln, "\n"]+ cl = csiHeader >> string "2K" >> return ()+ silenceCSI = do+ txt <- AttoText.takeWhile (not . isEsc . fromEnum)+ _ <- AttoText.many1 csi+ return txt++-- CSI (Control Sequence Introducer) sequences+csi :: Parser Text+csi = do+ _ <- csiHeader+ param <- takeWhileInRange 0x30 0x3F+ inter <- takeWhileInRange 0x20 0x2F+ final <- satisfy ((inRange 0x40 0x7E) . fromEnum)+ return $ Text.concat [param, inter, Text.singleton final]+ where+ takeWhileInRange l u = AttoText.takeWhile (inRange l u . fromEnum)+ inRange l u i | i >= l && i <= u = True+ inRange _ _ _ = False++csiHeader :: Parser ()+csiHeader = (satisfy (isEsc . fromEnum) <* char '[') >> return ()++isEsc :: Int -> Bool+isEsc 27 = True+isEsc _ = False
src/Codec/Sarsi/Rust.hs view
@@ -1,37 +1,27 @@ {-# LANGUAGE OverloadedStrings #-}-module Codec.Sarsi.Rust where -import Codec.Sarsi (Level(..), Location(..), Message(..))+module Codec.Sarsi.Rust where +import Codec.Sarsi (Level (..), Location (..), Message (..)) import Data.Attoparsec.Text messageParser :: Parser Message-messageParser = choice [formatA, formatB] where- formatA = do- fp <- takeWhile1 (\c -> c /= sepChar && c /= '\n' && c /= '\r') <* char sepChar- n <- decimal <* char sepChar- c <- decimal <* char sepChar- _ <- untilSep <* char sepChar- _ <- untilSpace *> space- l <- choice- [ string "warning: " *> return Warning- , string "error: " *> return Error ]- txt <- untilLineBreak <* ("\n" <* end)- return $ Message (Location fp c n) l [txt]-- formatB = do- l <- choice- [ string "warning" *> untilSep0 *> string ": " *> return Warning- , string "error" *> untilSep0 *> string ": " *> return Error ]- txt <- untilLineBreak <* "\n"- fp <- many1 space *> "--> " *> untilSep <* char sepChar- n <- decimal <* char sepChar- c <- decimal <* end- return $ Message (Location fp c n) l [txt]-- untilSep = takeWhile1 $ \w -> w /= sepChar- untilSep0 = Data.Attoparsec.Text.takeWhile $ \w -> w /= ':'- untilSpace = takeWhile1 $ \w -> w /= ' '+messageParser = do+ l <-+ choice+ [ string "warning" *> untilSep0 *> string ": " *> return Warning,+ string "error" *> untilSep0 *> string ": " *> return Error+ ]+ body <- untilLineBreak <* "\n"+ fp <- many1 space *> "--> " *> untilSep <* char sepChar+ n <- decimal <* char sepChar+ c <- decimal <* "\n"+ comments <- many' (untilLineBreak <* "\n")+ _ <- end+ return $ Message (Location fp c n) l (body : comments)+ where+ untilSep = takeWhile1 $ \w -> w /= sepChar+ untilSep0 = Data.Attoparsec.Text.takeWhile $ \w -> w /= ':' end = choice [const () <$> "\n", endOfInput, return ()] sepChar = ':'- untilLineBreak = takeWhile1 $ \w -> w /= '\n' && w /= '\r'+ untilLineBreak = takeWhile1 $ \w -> w /= '\n'
src/Codec/Sarsi/SBT.hs view
@@ -1,67 +1,63 @@ {-# LANGUAGE OverloadedStrings #-}+ module Codec.Sarsi.SBT where -import Codec.Sarsi (Level(..), Location(..), Message(..))+import Codec.Sarsi (Level (..), Location (..), Message (..)) import Data.Attoparsec.Combinator (lookAhead) import Data.Attoparsec.Text-import Data.Text (Text)- import qualified Data.Attoparsec.Text as AttoText+import Data.Text (Text) import qualified Data.Text as Text data SBTEvent = CompileStart Text | TaskFinish Bool Text | Throw Message- deriving Show+ deriving (Show) +cleaningCursesSBT :: Parser Text+cleaningCursesSBT = choice [silent, empty, keep]+ where+ silent = " | =>" >> untilLineBreak >> "\n" >> return Text.empty+ empty = do+ _ <- AttoText.takeWhile1 $ \w -> w == '\n'+ return Text.empty+ keep = do+ content <- (AttoText.takeWhile1 $ \w -> w /= '\n') <* end+ return $ content `Text.snoc` '\n'+ eventParser :: Parser SBTEvent eventParser = choice [compile, finish, Throw <$> messageParser] where compile = do- txt <- string "[info] Compiling " *> untilLineBreak <* end+ txt <- string "[info] " *> choice ["Build triggered", "Compiling"] *> untilLineBreak <* end return $ CompileStart txt finish = do res <- status <* space txt <- string "Total time: " *> untilLineBreak <* end- _ <- end+ _ <- end return $ TaskFinish res txt- where- status = choice [string "[success]" *> return True, string "[error]" *> return False]+ where+ status = choice [string "[success]" *> return True, string "[error]" *> return False] messageParser :: Parser Message messageParser = do lvl <- lineStart- fp <- takeWhile1 (\c -> c /= sepChar && c /= '\n' && c /= '\r') <* char sepChar- ln <- decimal <* char sepChar- t <- space *> (untilLineBreak <* "\n")- ts <- manyTill' (lineStart *> (untilLineBreak <* "\n")) (lookAhead $ column')- col <- column'- _ <- end- return $ Message (Location fp (fromIntegral col) ln) lvl $ formatTxts t ts- where- level = choice [string "[error]" *> return Error, string "[warn]" *> return Warning]- lineStart = level <* space- sepChar = ':'- formatTxts t [] = [t]- formatTxts t ts = t : init ts- column' = level *> ((length <$> many1 space) <* "^\n")--cleanEC :: Parser Text-cleanEC = choice [noEC, withEC]+ fp <- takeWhile1 (\c -> c /= sepChar && c /= '\n') <* char sepChar+ ln <- decimal <* char sepChar+ col <- decimal <* char sepChar+ t <- space *> (untilLineBreak <* "\n")+ ts <- manyTill' (lineStart *> (untilLineBreak <* "\n")) (lookAhead $ column')+ _ <- column' -- ignored as it was parsed above+ _ <- end+ return $ Message (Location fp col ln) lvl $ formatTxts t ts where- noEC = takeStart <* endOfInput- withEC = do- before <- takeStart <* anyChar- _ <- takeEnd <* anyChar- after <- cleanEC- return (Text.concat [before, after])- takeStart = AttoText.takeWhile (not . isEscStart . fromEnum)- takeEnd = AttoText.takeWhile (not . isEscEnd . fromEnum)- isEscStart 27 = True- isEscStart _ = False- isEscEnd 109 = True- isEscEnd _ = False+ level = choice [string "[error]" *> return Error, string "[warn]" *> return Warning]+ lineStart = level <* space+ sepChar = ':'+ formatTxts t [] = [t]+ formatTxts t ts = t : init ts+ column' = level *> ((length <$> many1 space) <* "^\n") untilLineBreak :: Parser Text-untilLineBreak = takeWhile1 $ \w -> w /= '\n' && w /= '\r'+untilLineBreak = takeWhile1 $ \w -> w /= '\n' end :: Parser () end = choice [const () <$> "\n", endOfInput, return ()]
src/Codec/Sarsi/SBT/Machine.hs view
@@ -1,12 +1,11 @@ module Codec.Sarsi.SBT.Machine where -import Codec.Sarsi (Event(..), Message(..), Level(..))-import Codec.Sarsi.SBT (SBTEvent(..), eventParser, cleanEC, untilLineBreak, end)-import Data.Attoparsec.Text.Machine (processParser, streamParser)-import Data.Int (Int64)-import Data.Machine (ProcessT, (<~), asParts, auto, scan)+import Codec.Sarsi (Event (..), Level (..), Message (..))+import Codec.Sarsi.Curses (cleaningCurses)+import Codec.Sarsi.SBT (SBTEvent (..), cleaningCursesSBT, eventParser)+import Data.Attoparsec.Text.Machine (asLines, processParser, streamParser)+import Data.Machine (ProcessT, asParts, auto, filtered, scan, (<~)) import Data.Text (Text)- import qualified Data.Text as Text eventProcess :: Monad m => ProcessT m Text Event@@ -14,34 +13,33 @@ where f (session, _) event = runSession session event unpack (_, Just e) = [e]- unpack _ = []+ unpack _ = [] eventProcess' :: Monad m => ProcessT m Text SBTEvent eventProcess' = asParts <~ auto unpackEvent <~ streamParser eventParser <~ preprocessing where- preprocessing =- asParts <~ auto unpackEC <~ processParser cleanEC <~ asParts <~ auto unpackLine <~ streamParser byLine- where byLine = untilLineBreak <* end- unpackEvent (Right e) = [e]- unpackEvent (Left _) = []- unpackEC (Left _) = []- unpackEC (Right (_, txt)) = [txt]- unpackLine (Right txt) = [txt `Text.snoc` '\n']- unpackLine (Left _) = []-+ preprocessing = filtered (not . Text.null) <~ parsing cleaningCursesSBT <~ parsing cleaningCurses <~ input+ where+ input = auto (\txt -> Text.snoc txt '\n') <~ asLines+ parsing p = asParts <~ auto f <~ processParser p+ where+ f (Left _) = []+ f (Right (_, txt)) = [txt]+ unpackEvent (Right e) = [e]+ unpackEvent (Left _) = [] -data Session = Session { isBuilding :: Bool, counts :: (Int64, Int64) }- deriving Show+data Session = Session {isBuilding :: Bool, counts :: (Int, Int)}+ deriving (Show) emptySession :: Session emptySession = Session False (0, 0) runSession :: Session -> SBTEvent -> (Session, Maybe Event)-runSession (Session False f) (CompileStart _) = (Session True f, Just $ Start $ Text.pack "scala")-runSession (Session False f) _ = (Session False f, Nothing)-runSession (Session True f) (CompileStart _) = (Session True f, Nothing)-runSession (Session True (e,w)) (TaskFinish _ _) = (Session False (0, 0), Just $ Finish e w)-runSession (Session True f) (Throw msg) = (Session True (inc msg f), Just $ Notify msg)+runSession (Session False f) (CompileStart _) = (Session True f, Just $ Start $ Text.pack "scala")+runSession (Session False f) _ = (Session False f, Nothing)+runSession (Session True f) (CompileStart _) = (Session True f, Nothing)+runSession (Session True (e, w)) (TaskFinish _ _) = (Session False (0, 0), Just $ Finish e w)+runSession (Session True f) (Throw msg) = (Session True (inc msg f), Just $ Notify msg) where inc (Message _ Warning _) (e, w) = (e, w + 1)- inc (Message _ Error _) (e, w) = (e + 1, w)+ inc (Message _ Error _) (e, w) = (e + 1, w)
src/Data/Attoparsec/Machine.hs view
@@ -1,20 +1,23 @@ {-# LANGUAGE Rank2Types #-}+ module Data.Attoparsec.Machine where -import Data.Attoparsec.Internal.Types (IResult(..))-import Data.Machine (MachineT(..), ProcessT, Step(Await, Yield), Is(Refl), stopped)+import Data.Attoparsec.Internal.Types (IResult (..))+import Data.Machine (Is (Refl), MachineT (..), ProcessT, Step (Await, Yield), stopped) streamParserWith :: (Monoid i, Monad m) => (i -> IResult i a) -> ProcessT m i (Either String a)-streamParserWith runParser = start where- start = MachineT . return $ Await parse Refl stopped- parse i = MachineT . return . f $ runParser i- f (Fail _ _ e) = Yield (Left e) start- f (Partial c) = Await (MachineT . return . f . c) Refl $ (MachineT . return . f $ c mempty)- f (Done i r) = Yield (Right r) (parse i)+streamParserWith runParser = start+ where+ start = MachineT . return $ Await parse Refl stopped+ parse i = MachineT . return . f $ runParser i+ f (Fail _ _ e) = Yield (Left e) start+ f (Partial c) = Await (MachineT . return . f . c) Refl $ (MachineT . return . f $ c mempty)+ f (Done i r) = Yield (Right r) (parse i) processParserWith :: (Monoid i, Monad m) => (i -> IResult i a) -> ProcessT m i (Either String (i, a))-processParserWith runParser = MachineT . return $ Await parse Refl stopped where- parse i = MachineT . return . f $ runParser i- f (Fail _ _ e) = Yield (Left e) (processParserWith runParser)- f (Partial c) = f $ c mempty- f (Done i a) = Yield (Right (i, a)) (processParserWith runParser)+processParserWith runParser = MachineT . return $ Await parse Refl stopped+ where+ parse i = MachineT . return . f $ runParser i+ f (Fail _ _ e) = Yield (Left e) (processParserWith runParser)+ f (Partial c) = f $ c mempty+ f (Done i a) = Yield (Right (i, a)) (processParserWith runParser)
src/Data/Attoparsec/Text/Machine.hs view
@@ -1,13 +1,20 @@-module Data.Attoparsec.Text.Machine where+{-# LANGUAGE OverloadedStrings #-} -import Data.Attoparsec.Text (Parser, parse)-import Data.Text (Text)-import Data.Machine (ProcessT)+module Data.Attoparsec.Text.Machine where import Data.Attoparsec.Machine (processParserWith, streamParserWith)+import Data.Attoparsec.Text (Parser, parse, takeWhile)+import Data.Machine (ProcessT, asParts, auto, (<~))+import Data.Text (Text) -streamParser :: Monad m => Parser a -> ProcessT m Text (Either String a)-streamParser p = streamParserWith $ parse p+asLines :: Monad m => ProcessT m Text Text+asLines = asParts <~ auto unpackLine <~ streamParser ((Data.Attoparsec.Text.takeWhile $ \w -> w /= '\n') <* "\n")+ where+ unpackLine (Right txt) = [txt]+ unpackLine (Left _) = [] processParser :: Monad m => Parser a -> ProcessT m Text (Either String (Text, a)) processParser p = processParserWith $ parse p++streamParser :: Monad m => Parser a -> ProcessT m Text (Either String a)+streamParser p = streamParserWith $ parse p
+ src/Rosetta.hs view
@@ -0,0 +1,60 @@+module Rosetta where++import Data.List (find)++fromExtension :: String -> Maybe LanguageTag+fromExtension ext = find (\t -> languageExtension t == ext) languageTags++fromTool :: String -> Maybe ProjectTag+fromTool tool = find (\t -> projectTool t == tool) projectTags++data LanguageTag = CS | FS | HS | JV | NX | RS | SC+ deriving (Bounded, Enum, Eq, Ord)++languageExtension :: LanguageTag -> String+languageExtension CS = "cs"+languageExtension FS = "fs"+languageExtension HS = "hs"+languageExtension JV = "java"+languageExtension NX = "nix"+languageExtension RS = "rs"+languageExtension SC = "scala"++languageTags :: [LanguageTag]+languageTags = [minBound ..]++languageLabel :: LanguageTag -> String+languageLabel CS = "csharp"+languageLabel FS = "fsharp"+languageLabel HS = "haskell"+languageLabel JV = "java"+languageLabel NX = "nix"+languageLabel RS = "rust"+languageLabel SC = "scala"++data ProjectTag = CABAL | CARGO | DOTNET | NIX | SBT | STACK+ deriving (Bounded, Enum, Eq, Ord)++projectTags :: [ProjectTag]+projectTags = [minBound ..]++projectLanguages :: ProjectTag -> [LanguageTag]+projectLanguages CABAL = [HS]+projectLanguages CARGO = [RS]+projectLanguages DOTNET = [CS, FS]+projectLanguages NIX = [NX]+projectLanguages SBT = [JV, SC]+projectLanguages STACK = [HS]++projectTool :: ProjectTag -> String+projectTool CABAL = "cabal"+projectTool CARGO = "cargo"+projectTool DOTNET = "dotnet"+projectTool NIX = "nix-shell"+projectTool SBT = "sbt"+projectTool STACK = "stack"++projectToolBuildArgs :: ProjectTag -> [String]+projectToolBuildArgs NIX = ["."]+projectToolBuildArgs SBT = ["compile"]+projectToolBuildArgs _ = ["build"]
src/Sarsi.hs view
@@ -2,11 +2,10 @@ import Crypto.Hash (Digest, hash) import Crypto.Hash.Algorithms (MD5)-import Network.Socket (Family(AF_UNIX), SockAddr(SockAddrUnix), Socket, SocketType(Stream), defaultProtocol, socket)-import System.Directory (getTemporaryDirectory, createDirectory, doesDirectoryExist, doesFileExist, makeAbsolute, removeFile)-import System.FilePath ((</>))- import qualified Data.ByteString.Char8 as BSC8+import Network.Socket (Family (AF_UNIX), SockAddr (SockAddrUnix), Socket, SocketType (Stream), defaultProtocol, socket)+import System.Directory (XdgDirectory (XdgCache), createDirectory, doesDirectoryExist, doesFileExist, getXdgDirectory, makeAbsolute, removeFile)+import System.FilePath ((</>)) title :: String title = "sarsi"@@ -17,15 +16,14 @@ getBroker :: IO Broker getBroker = do- tmp <- getTemporaryDirectory- let bp = tmp </> title+ bp <- getXdgDirectory XdgCache title let broker = Broker bp exists <- doesDirectoryExist bp if exists then return broker else createDirectory bp >> return broker getTopic :: Broker -> FilePath -> IO Topic getTopic b@(Broker bp) fp' = do- fp <- makeAbsolute fp'+ fp <- makeAbsolute fp' return $ Topic b $ bp </> (show $ (hash $ BSC8.pack fp :: Digest MD5)) removeTopic :: Topic -> IO ()
src/Sarsi/Consumer.hs view
@@ -1,29 +1,30 @@ {-# LANGUAGE Rank2Types #-}+ module Sarsi.Consumer where import Codec.Sarsi (Event, getEvent)-import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)+import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar) import Control.Exception (IOException, bracket, try)-import Data.Binary.Machine (processGet)-import Data.Machine ((<~), auto, asParts)+import Data.Binary.Machine (streamGet)+import Data.Machine (asParts, auto, (<~)) import Network.Socket (connect, socketToHandle)-import Sarsi (Broker(..), Topic(..), createSocket, getSockAddr)+import Sarsi (Broker (..), Topic (..), createSocket, getSockAddr) import System.FSNotify (eventPath, watchDir, withManager)-import System.IO (IOMode(ReadMode), hClose, hWaitForInput)+import System.IO (IOMode (ReadMode), hClose, hWaitForInput) import System.IO.Machine (IOSource, byChunkOf, sourceHandle) consumeOrWait :: Topic -> (Maybe s -> IOSource Event -> IO (Either s a)) -> IO a consumeOrWait topic@(Topic (Broker bp) tp) f = do res <- consume topic f either (const $ withManager waitAndRetry) return res- where- waitAndRetry mng = do- lck <- newEmptyMVar- stop <- watchDir mng bp pred' $ const $ putMVar lck ()- takeMVar lck- stop- consumeOrWait topic f- pred' e = eventPath e == tp+ where+ waitAndRetry mng = do+ lck <- newEmptyMVar+ stop <- watchDir mng bp pred' $ const $ putMVar lck ()+ takeMVar lck+ stop+ consumeOrWait topic f+ pred' e = eventPath e == tp consume :: Topic -> (Maybe s -> IOSource Event -> IO (Either s a)) -> IO (Either IOException a) consume topic f = try $ consume' topic f@@ -32,13 +33,14 @@ consume' topic f = bracket createHandle hClose (process Nothing) where createHandle = do- sock <- createSocket+ sock <- createSocket connect sock $ getSockAddr topic socketToHandle sock ReadMode process s h = do- sa <- f s $ asParts <~ auto unpack <~ processGet getEvent <~ sourceHandle (byChunkOf 1) h- _ <- hWaitForInput h (-1)+ sa <- f s $ asParts <~ auto unpack <~ streamGet getEvent <~ sourceHandle (byChunkOf 1) h+ _ <- hWaitForInput h (-1) either (continue h) return sa- where continue h' s' = process (Just s') h'+ where+ continue h' s' = process (Just s') h' unpack (Right e) = [e] unpack (Left _) = []
+ src/Sarsi/Processor.hs view
@@ -0,0 +1,52 @@+module Sarsi.Processor where++import qualified Codec.GHC.Log as GHC+import Codec.Sarsi (Message)+import Codec.Sarsi.GHC (fromGHCLog)+import qualified Codec.Sarsi.Rust as Rust+import Data.Attoparsec.Text.Machine (streamParser)+import Data.Machine (ProcessT, asParts, auto, flattened, (<~))+import Data.Machine.Fanout (fanout)+import Data.Set (Set)+import qualified Data.Set as Set+import Data.Text (Text)+import Rosetta (LanguageTag (..), ProjectTag, projectLanguages)++data Processor = Processor {language :: LanguageTag, process :: ProcessT IO Text Message}++instance Eq Processor where+ a == b = (language a) == (language b)++instance Ord Processor where+ compare a b = compare (language a) (language b)++projectProcessors :: ProjectTag -> Set Processor+-- projectProcessors DOTNET = processDotnet+projectProcessors project = Set.fromList $ g =<< f <$> projectLanguages project+ where+ f l = (\p -> (l, p)) <$> languageProcess l+ g (Just (l, p)) = [Processor {language = l, process = p}]+ g Nothing = []++languageProcess :: LanguageTag -> Maybe (ProcessT IO Text Message)+languageProcess HS = Just processHaskell+languageProcess RS = Just processRust+languageProcess _ = Nothing++processAll :: [ProcessT IO Text Message] -> ProcessT IO Text Message+processAll xs = flattened <~ (fanout $ (\p -> (auto (\x -> [x])) <~ p) <$> xs)++processAny :: ProcessT IO Text Message+processAny = processAll [processHaskell, processRust]++processHaskell :: ProcessT IO Text Message+processHaskell = asParts <~ auto unpack <~ streamParser GHC.messageParser+ where+ unpack (Right msg) = [fromGHCLog msg]+ unpack (Left _) = []++processRust :: ProcessT IO Text Message+processRust = asParts <~ auto unpack <~ streamParser Rust.messageParser+ where+ unpack (Right msg) = [msg]+ unpack (Left _) = []
src/Sarsi/Producer.hs view
@@ -1,67 +1,97 @@ {-# LANGUAGE Rank2Types #-}+ module Sarsi.Producer where -import Codec.Sarsi (Event(Start), putEvent)-import Control.Exception (IOException, bracket, tryJust)+import Codec.Sarsi (Event (..), Level (..), Message (..), putEvent) import Control.Concurrent.Async (async, cancel, wait) import Control.Concurrent.Chan (dupChan, newChan, readChan, writeChan) import Control.Concurrent.MVar (modifyMVar_, newMVar, readMVar) import Control.Concurrent.STM (atomically) import Control.Concurrent.STM.TQueue (newTQueue, tryReadTQueue, writeTQueue)+import Control.Exception (IOException, bracket, tryJust) import Data.Binary.Machine (processPut)-import Data.Machine (ProcessT, (<~), runT_, sinkPart_, prepended)+import Data.List (foldl')+import Data.Machine (ProcessT, prepended, runT_, (<~))+import Data.Machine.Process (takingJusts) import Network.Socket (Socket, accept, bind, close, listen, socketToHandle)-import Sarsi (Topic, createSocket, createSockAddr, removeTopic)-import System.IO (IOMode(WriteMode), Handle, hClose)-import System.IO.Machine (byChunk, sinkIO, sinkHandle, sourceIO)+import Sarsi (Topic, createSockAddr, createSocket, removeTopic, title)+import System.Console.ANSI+import System.IO (Handle, IOMode (WriteMode), hClose)+import System.IO.Machine (byChunk, sinkHandle, sinkIO, sourceIO) +finishPrint :: Int -> Int -> IO ()+finishPrint e w = do+ setSGR (sgr e w)+ putStr $ title+ setSGR [Reset]+ putStr $ ": "+ putStrLn $ show event+ where+ sgr 0 0 = [SetColor Foreground Dull Green]+ sgr 0 _ = [SetColor Foreground Dull Yellow]+ sgr _ _ = [SetColor Foreground Vivid Red]+ event = Finish e w++finishCreate :: [Event] -> (Int, Int)+finishCreate xs = foldl' f empty xs+ where+ empty = (0, 0)+ f (e, w) (Notify (Message _ Warning _)) = (e, (w + 1))+ f (e, w) (Notify (Message _ Error _)) = ((e + 1), w)+ f finish _ = finish+ produce :: Topic -> (ProcessT IO Event Event -> IO a) -> IO a produce t f = do- conns <- atomically $ newTQueue- chan <- newChan- state <- newMVar []- server <- async $ bracket bindSock close (serve (process conns chan state))- feeder <- async $ f $ sinkPart_ (\x -> (x, x)) (sinkIO $ feed chan state)- a <- wait feeder+ conns <- atomically $ newTQueue+ chan <- newChan+ state <- newMVar []+ server <- async $ bracket bindSock close (serve (process conns chan state))+ feeder <- async $ f (sinkIO $ feed chan state)+ a <- wait feeder+ es <- readMVar state+ let (errs, warns) = finishCreate es+ writeChan chan $ Just $ Finish errs warns+ writeChan chan $ Nothing+ finishPrint errs warns waitFinish conns cancel server removeTopic t return a- where- bindSock = do- sock <- createSocket- addr <- createSockAddr t- bind sock addr- listen sock 1- return sock- process conns chan' state h = do- chan <- dupChan chan'- es <- readMVar state- conn <- async $ do- runT_ $ sinkHandle byChunk h <~ processPut putEvent <~ (prepended $ reverse es) <~ (sourceIO $ readChan chan)- hClose h- atomically $ writeTQueue conns conn- return Nothing- feed chan state e = do- modifyMVar_ state $ case e of- (Start _) -> const $ return [e]- _ -> return . (:) e- writeChan chan e- waitFinish conns = do- conn <- atomically $ tryReadTQueue conns- _ <- tryJust io $ maybe (return ()) wait conn- return ()- where- io :: IOException -> Maybe ()- io _ = Just ()+ where+ bindSock = do+ sock <- createSocket+ addr <- createSockAddr t+ bind sock addr+ listen sock 1+ return sock+ process conns chan' state h = do+ chan <- dupChan chan'+ es <- readMVar state+ conn <- async $ do+ runT_ $ sinkHandle byChunk h <~ processPut putEvent <~ (prepended $ reverse es) <~ takingJusts <~ (sourceIO $ readChan chan)+ hClose h+ atomically $ writeTQueue conns conn+ return Nothing+ feed chan state e = do+ modifyMVar_ state $ case e of+ (Start _) -> const $ return [e]+ _ -> return . (:) e+ writeChan chan $ Just e+ waitFinish conns = do+ conn <- atomically $ tryReadTQueue conns+ _ <- tryJust io $ maybe (return ()) wait conn+ return ()+ where+ io :: IOException -> Maybe ()+ io _ = Just () serve :: (Handle -> IO (Maybe a)) -> Socket -> IO a serve f sock = bracket acceptHandle hClose process where acceptHandle = do (conn, _) <- accept sock- h <- socketToHandle conn WriteMode+ h <- socketToHandle conn WriteMode return h process h = do- a <- f h+ a <- f h maybe (serve f sock) return a
+ src/Sarsi/Tools/Pipe.hs view
@@ -0,0 +1,40 @@+module Sarsi.Tools.Pipe where++import Codec.Sarsi (Event (..), Message (..))+import Codec.Sarsi.Curses (cleanLine, cleaningCurses)+import Data.Attoparsec.Text.Machine (processParser)+import Data.ByteString (ByteString)+import qualified Data.ByteString.Char8 as ByteString+import Data.Machine (MachineT, ProcessT, asParts, auto, autoM, prepended, runT_, (<~))+import Data.Text (Text)+import qualified Data.Text as Text+import Data.Text.Encoding (decodeUtf8)+import Sarsi (getBroker, getTopic)+import Sarsi.Producer (produce)+import System.Exit (ExitCode (ExitSuccess), exitWith)+import System.IO (stdin, stdout)+import System.IO.Machine (byLine, sourceHandle)++pipe :: String -> ProcessT IO Text Message -> IO ()+pipe lbl process =+ pipeFrom lbl process $+ autoM echo <~ (sourceHandle byLine stdin)+ where+ echo xs = ByteString.hPutStrLn stdout xs >> return xs++pipeFrom :: String -> ProcessT IO Text Message -> MachineT IO k ByteString -> IO ()+pipeFrom lbl process source = do+ b <- getBroker+ t <- getTopic b "."+ produce t $ producer lbl process source+ exitWith ExitSuccess++producer :: String -> ProcessT IO Text Message -> MachineT IO k ByteString -> ProcessT IO Event Event -> IO ()+producer lbl process source sink = do+ runT_ $ pipeline <~ process <~ cleaning <~ auto decodeUtf8 <~ source+ where+ pipeline = sink <~ prepended [Start $ Text.pack lbl] <~ auto Notify+ cleaning = asParts <~ auto unpack <~ processParser cleaningCurses <~ auto (\txt -> (cleanLine txt) `Text.snoc` '\n')+ where+ unpack (Right (_, txt)) = [txt]+ unpack (Left _) = []
− src/Sarsi/Tools/Shell.hs
@@ -1,61 +0,0 @@-module Sarsi.Tools.Shell where--import Codec.Sarsi (Event(..), Level(..), Message(..))-import Data.Machine (ProcessT, Is, (<~), auto, autoM, prepended, runT, runT_, source, wye, repeatedly, awaits, yield, Y(Z))-import Data.List (foldl')-import Data.Text (Text)-import Sarsi (getBroker, getTopic)-import Sarsi.Producer (produce)-import System.Environment (getArgs)-import System.IO (stderr, stdout)-import System.IO.Machine (byLine)-import System.Process (StdStream(..), shell, std_in, std_err, std_out)-import System.Process.Machine (ProcessMachines, callProcessMachines)-import System.Exit (ExitCode, exitWith)--import qualified Data.List as List-import qualified Data.Text as Text-import qualified Data.Text.IO as TextIO-import qualified Sarsi as Sarsi---- TODO Contribute to machine-process (as alt to mStdErr, mStdOut)-mStdOutputs :: ProcessT IO (Either a a) b -> ProcessMachines a a0 (Is a0) -> IO [b]-mStdOutputs mp (_, Just stdOut, Just stdErr) = runT $ mp <~ wye stdErr stdOut slurp where- slurp = repeatedly $ do- res <- awaits Z- yield res-mStdOutputs _ _ = return []--callShell :: String -> ProcessT IO Text Message -> ProcessT IO Message a -> IO (ExitCode, [a])-callShell cmd parser sink = callProcessMachines byLine createProc (mStdOutputs pipeline)- where- pipeline = sink <~ parser <~ appendCR <~ echo- echo = autoM $ \res -> case res of- Left txt -> TextIO.hPutStrLn stderr txt >> return txt- Right txt -> TextIO.hPutStrLn stdout txt >> return txt- createProc = (shell cmd) { std_in = Inherit, std_err = CreatePipe, std_out = CreatePipe }- appendCR = auto $ (`Text.snoc` '\n')--producer :: String -> String -> ProcessT IO Text Message -> ProcessT IO Event Event -> IO ExitCode-producer lbl cmd parser sink = do- (ec, xs) <- callShell cmd parser pipeline- let finish = createFinish xs- putStrLn $ concat [Sarsi.title, ": ", show finish]- runT_ $ sink <~ source [finish]- return ec- where- pipeline = sink <~ prepended [Start $ Text.pack lbl] <~ auto Notify- createFinish xs = foldl' f empty xs- where- empty = Finish 0 0- f (Finish e w) (Notify (Message _ Warning _)) = Finish e (w + 1)- f (Finish e w) (Notify (Message _ Error _)) = Finish (e + 1) w- f finish _ = finish--mainShell :: String -> ProcessT IO Text Message -> IO ()-mainShell lbl parser = do- args <- getArgs- b <- getBroker- t <- getTopic b "."- ec <- produce t $ producer lbl (concat $ List.intersperse " " args) parser- exitWith ec
src/Sarsi/Tools/Trace.hs view
@@ -1,15 +1,29 @@ module Sarsi.Tools.Trace where -import Data.Machine ((<~), auto, runT_)+import qualified Codec.GHC.Log as GHC+import Codec.Sarsi.Curses (cleanLine, cleaningCurses)+import qualified Codec.Sarsi.Rust as Rust+import qualified Codec.Sarsi.SBT as SBT+import qualified Codec.Sarsi.SBT.Machine as SBTM import Data.Attoparsec.Text (Parser) import Data.Attoparsec.Text.Machine (streamParser)+import Data.Machine (ProcessT, asParts, auto, autoM, runT_, (<~))+import Data.Text (Text)+import qualified Data.Text as Text+import qualified Data.Text.IO as TextIO import System.IO (Handle) import System.IO.Machine (byLine, printer, sourceHandle) -import qualified Codec.GHC.Log as GHC-import qualified Codec.Sarsi.Rust as Rust-import qualified Codec.Sarsi.SBT as SBT-import qualified Data.Text as Text+traceCleanCurses :: Handle -> IO ()+traceCleanCurses h = runT_ $ autoM printing <~ preprocessing <~ appendLF <~ sourceHandle byLine h+ where+ printing (Right s) = TextIO.putStrLn s+ printing e = putStrLn $ show e+ preprocessing = streamParser cleaningCurses <~ asParts <~ auto unpackLine <~ asLines+ where+ asLines = streamParser $ SBT.untilLineBreak <* SBT.end+ unpackLine (Right txt) = [(cleanLine txt) `Text.snoc` '\n']+ unpackLine (Left _) = [] traceHS :: Handle -> IO () traceHS = traceParser GHC.messageParser@@ -20,10 +34,12 @@ traceSBT :: Handle -> IO () traceSBT = traceParser SBT.eventParser +traceSBTCurses :: Handle -> IO ()+traceSBTCurses h = runT_ $ printer <~ SBTM.eventProcess' <~ appendLF <~ sourceHandle byLine h+ traceParser :: Show a => Parser a -> Handle -> IO () traceParser parser h = do- runT_ $ printer <~ streamParser parser <~ appendCR <~ sourceHandle byLine h- where- appendCR = auto $ (`Text.snoc` '\n')-+ runT_ $ printer <~ streamParser parser <~ appendLF <~ sourceHandle byLine h +appendLF :: ProcessT IO Text Text+appendLF = auto $ (`Text.snoc` '\n')