tabloid 0.2 → 0.3
raw patch · 22 files changed
+1315/−1 lines, 22 files
Files
- Component.hs +28/−0
- DataProcController.hs +56/−0
- DataProcView.hs +30/−0
- DataSourceView.hs +34/−0
- FileChooser.hs +42/−0
- LineSplitterController.hs +135/−0
- LineSplitterView.hs +39/−0
- LoadSaveController.hs +95/−0
- LoadSaveView.hs +25/−0
- MultiWidgetContainerController.hs +52/−0
- MultiWidgetContainerView.hs +37/−0
- PerformanceTuning.hs +41/−0
- README +6/−0
- RegexDefinitionController.hs +95/−0
- RegexDefinitionView.hs +25/−0
- ShellCmdInputController.hs +111/−0
- ShellCmdInputView.hs +68/−0
- SimpleRegex.hs +99/−0
- TableController.hs +188/−0
- TableView.hs +86/−0
- WindowedApp.hs +22/−0
- tabloid.cabal +1/−1
+ Component.hs view
@@ -0,0 +1,28 @@+module Component where++import Data.IORef+import Control.Applicative++-- reference management:+newtype Ref a = Ref (IORef a)++infixr 0 .<, .<<, .>, .>>++(.>>) :: Ref (state) -> (state -> IO a) -> IO a+(.>>) (Ref c) a = readIORef c >>= a++(.<<) :: Ref (state) -> (state -> IO state) -> IO ()+(.<<) (Ref c) a = readIORef c >>= a >>= writeIORef c++(.<) :: Ref (state) -> (state -> state) -> IO ()+(.<) (Ref c) a = readIORef c >>= writeIORef c . a++(.>) :: Ref (state) -> (state -> a) -> IO a+(.>) c getter = c .>> return . getter+ +newRef :: state -> IO (Ref (state))+newRef c = Ref <$> newIORef c ++unRef :: Ref (state) -> IO (state)+unRef (Ref c) = readIORef c+
+ DataProcController.hs view
@@ -0,0 +1,56 @@+module DataProcController where++import Graphics.UI.Gtk+import Control.Monad+import Control.Applicative++import qualified DataProcView as View+import qualified ShellCmdInputController as Shell+import qualified LineSplitterController as RxDef+import qualified TableController as Table++import qualified Data.ByteString.Lazy.Char8 as L+import SimpleRegex+ +import WindowedApp+import Component++type Controller = Ref C++view = View.mainWidget . gui++new :: IO Controller+new = do+ s <- Shell.new+ r <- RxDef.new+ t <- Table.new+ sv <- (s.>Shell.view) + rv <- (r.>RxDef.view) + tv <- (t.>Table.view)+ g <- View.new sv rv tv + this <- newRef $ C g s r t RxDef.defaultFilter+ s .< Shell.onUpdate + (Just (\lines -> this .>> showData lines))+ r .< RxDef.onUpdate + (Just (\filt -> do+ this .< (\stat -> stat {filterFun = filt})+ postGUIAsync $ s .<< Shell.runText))+ return this++-- internal++showData :: L.ByteString -> C -> IO ()+showData x state = (filterFun state x) >>= Table.push (table state) ++data C = C {+ gui :: View.ViewState+ , shell :: Shell.Controller+ , rxdef :: RxDef.Controller+ , table :: Table.Controller+ , filterFun :: RxDef.Filter+ }++-- tests+test = windowedApp "DataProcController test" $ do+ t <- new :: IO Controller+ t .> view
+ DataProcView.hs view
@@ -0,0 +1,30 @@+module DataProcView where++import Graphics.UI.Gtk+import Graphics.UI.Gtk.ModelView as MV++data ViewState = V + {+ mainWidget :: Widget+ }++new :: Widget -> Widget -> Widget -> IO ViewState+new w1 w2 w3 = do+ vbox <- vBoxNew False 2+ vbox2 <- vBoxNew False 0++ w1ex <- expanderNew "Shell Command"+ expanderSetExpanded w1ex True+ containerAdd w1ex w1++ w2ex <- expanderNew "Splitter Regex"+ expanderSetExpanded w2ex False+ containerAdd w2ex w2++ boxPackStart vbox2 w1ex PackNatural 2+ boxPackEnd vbox2 w2ex PackNatural 2++ boxPackStart vbox vbox2 PackNatural 2+ boxPackStart vbox w3 PackGrow 2+ return (V (toWidget vbox))+
+ DataSourceView.hs view
@@ -0,0 +1,34 @@+module DataSourceView where++import Graphics.UI.Gtk+import Control.Monad+import Control.Applicative++data ViewState = V + { mainWidget :: Widget+ , cmdModeRB :: RadioButton+ , dynamicHaskellModeRB :: RadioButton+ }++new :: Widget -> IO ViewState+new shellModeW dynHaskellModeW = do+ vbox <- vBoxNew False 2+ hbox <- vBoxNew False 2+ + boxPackStart vbox hbox PackNatural 2+ boxPackEnd vbox rw PackGrow 2++++ linesplit `toggleButtonSetActive` False+ dis `toggleButtonSetActive` True++ boxPackStart hbox dis PackNatural 2+ boxPackStart hbox linesplit PackNatural 2+ boxPackStart hbox submatch PackNatural 2++ return $ V (toWidget vbox) dis submatch linesplit++ ++
+ FileChooser.hs view
@@ -0,0 +1,42 @@+module FileChooser + (+ openFileDialog+ , saveFileDialog+ ) where++import Graphics.UI.Gtk++saveFileDialog:: String -> (Maybe String) -> (FilePath -> IO ()) -> IO ()+saveFileDialog title fp action = fileDialog title "gtk-save" FileChooserActionSave action fp++openFileDialog:: String -> (Maybe String) -> (FilePath -> IO ()) -> IO ()+openFileDialog title fp action = fileDialog title "gtk-open" FileChooserActionOpen action fp+++fileDialog thetitle accepttitle accepttype action filterPattern = do+ dialog <- fileChooserDialogNew+ (Just $ thetitle)+ Nothing+ accepttype+ [("gtk-cancel" + ,ResponseCancel)+ ,(accepttitle + , ResponseAccept)]+ case filterPattern of + Nothing -> return ()+ Just filterPattern' ->+ do+ filt <- fileFilterNew+ fileFilterAddPattern filt filterPattern'+ set dialog [fileChooserFilter := filt]+ fileChooserSetPreviewWidgetActive dialog True+ widgetShow dialog+ response <- dialogRun dialog+ case response of + ResponseAccept -> do + Just fileName <- fileChooserGetFilename dialog+ action fileName+ ResponseCancel -> return ()+ ResponseDeleteEvent -> return ()+ widgetHide dialog+
+ LineSplitterController.hs view
@@ -0,0 +1,135 @@+module LineSplitterController + (+ new+ , test+ , onUpdate+ , view+ , Controller+ , Filter+ , OnUpdate+ , defaultFilter+ ) where++import Graphics.UI.Gtk+import Control.Monad+import Control.Applicative+import Data.Maybe++import WindowedApp+import Component+import SimpleRegex++import qualified LineSplitterView as View+import qualified RegexDefinitionController as RxDef+import qualified Data.ByteString.Lazy.Char8 as L+import SimpleRegex++type Controller = Ref C+type Filter = L.ByteString -> IO [[L.ByteString]]+type OnUpdate = Filter -> IO ()++view = View.mainWidget . gui++new :: IO Controller+new = do+ rx <- RxDef.new+ rxv <- rx .> RxDef.view+ v@(View.V mw dis sm ls ) <- View.new rxv + this <- newRef (C v Nothing defaultFilter rxv Nothing)+ onToggled dis $ do+ disA <- toggleButtonGetActive dis+ unless disA $ widgetSetSensitivity rxv True+ when disA (this .<< updateState)+ rx .< RxDef.onUpdate + (Just (\ s -> updateStateRx this s >> (this .<< updateState)))+ this .<< updateState+ return this++onUpdate :: Maybe OnUpdate -> C -> C+onUpdate u c = c { updateCB = u }++-- internal functions+defaultFilter :: Filter+defaultFilter s = do+ putStrLn "defaultFilter"+ return (map (:[]) (L.lines s))++updateStateRx :: Ref C -> Maybe (Regex,String) -> IO ()+updateStateRx this rx = do+ putStrLn $ "got new regex: " ++ maybe "Nothing" snd rx+ this .< (\c -> c {regex = rx})+ this .<< updateState++updateState :: C -> IO C+updateState state = do+ di <- toggleButtonGetActive (View.disableRB (gui state))+ sp <- toggleButtonGetActive (View.linesplitModeRB (gui state))+ sm <- toggleButtonGetActive (View.submatchModeRB (gui state))+ newFilter <- case (di, sp, sm) of+ (_, True, _) -> do+ postGUIAsync $ widgetSetSensitivity (regexDefView state) True+ makeRegexFilterFieldSep state + (_, _, True) -> do+ postGUIAsync $ widgetSetSensitivity (regexDefView state) True+ makeRegexFilterSubMatches state+ _ -> do+ postGUIAsync $ widgetSetSensitivity (regexDefView state) False+ return defaultFilter+ let newstate = (state {filterFun = newFilter})+ notifyUpdateCallback newstate+ return newstate++withMaybeRegex :: Maybe a -> (a -> Filter) -> Filter+withMaybeRegex Nothing _ = defaultFilter+withMaybeRegex (Just rx) f = f rx++makeRegexFilterSubMatches :: C -> IO Filter+makeRegexFilterSubMatches state = do+ return $ withMaybeRegex (regex state)+ (\(rx,src) indat -> do+ putStrLn $ "makeRegexFilterSubMatches" ++ src+ let sms = catSubmatches rx $ L.lines indat+ res = map (\ (ss, s) -> + case ss of+ [] -> [s]+ _ -> ss) sms+ return res)++ +makeRegexFilterFieldSep :: C -> IO Filter+makeRegexFilterFieldSep state = + return $ withMaybeRegex (regex state)+ (\(rx,src) d -> do+ putStrLn ("makeRegexFilterFieldSep " ++ src)+ let res = map (regexSplit rx) (L.lines d)+ return res)++notifyUpdateCallback :: C -> IO ()+notifyUpdateCallback state = + case updateCB state of+ Nothing -> do+ return ()+ Just updateCB' -> do + updateCB' (filterFun state)+ +data C = C {+ gui :: View.ViewState+ , updateCB :: Maybe OnUpdate+ , filterFun :: Filter+ , regexDefView :: Widget+ , regex :: Maybe (Regex, String)+ }++test = windowedApp "RegexDefinitionController test" $ do+ clut <- new+ mw <- View.mainWidget <$> (clut .> gui)+ clut .< onUpdate (Just tryNewFilter)+ return mw++testdata = L.unlines $ L.pack <$>+ [concat (map ((++ ";") . show) [x * 10 .. 9 + x * 10]) | + x <- [1..10]]++tryNewFilter f = do+ res <- (f testdata)+ putStrLn (take 100 (show res))
+ LineSplitterView.hs view
@@ -0,0 +1,39 @@+module LineSplitterView where++import Graphics.UI.Gtk+import Control.Monad+import Control.Applicative++data ViewState = V + {+ mainWidget :: Widget+ , disableRB :: RadioButton+ , submatchModeRB :: RadioButton+ , linesplitModeRB :: RadioButton+ }++new :: Widget -> IO ViewState+new rw = do+ vbox <- vBoxNew False 2+ hbox <- vBoxNew False 2+ + boxPackStart vbox hbox PackNatural 2+ boxPackEnd vbox rw PackGrow 2++ dis <- radioButtonNewWithLabel "Disabled"+ linesplit <- radioButtonNewWithLabelFromWidget dis "Field Seperation"+ submatch <- radioButtonNewWithLabelFromWidget dis "Use Submatches"++ linesplit `toggleButtonSetActive` False+ submatch `toggleButtonSetActive` False+ dis `toggleButtonSetActive` True++ boxPackStart hbox dis PackNatural 2+ boxPackStart hbox linesplit PackNatural 2+ boxPackStart hbox submatch PackNatural 2++ return $ V (toWidget vbox) dis submatch linesplit++ ++
+ LoadSaveController.hs view
@@ -0,0 +1,95 @@+module LoadSaveController + ( Controller+ , new+ , view+ , onSave+ , onLoad+ , clearLabel+ ) where++import qualified LoadSaveView as View+import qualified Data.ByteString.Lazy.Char8 as L+import Component+import Graphics.UI.Gtk+import WindowedApp+import FileChooser+import Control.Applicative+import Data.List++type Controller = Ref C+type LoadFun = L.ByteString -> IO ()+type SaveFun = IO (Maybe L.ByteString)++view :: C -> Widget+view = View.mainWidget . gui++new :: Maybe String -> IO Controller+new fnSuffix = do+ v <- View.new+ this <- newRef $ C v Nothing Nothing fnSuffix+ (View.saveB v) `onClicked` (this .>> save)+ (View.openB v) `onClicked` (this .>> load)+ return this++onSave :: Maybe SaveFun -> C -> C+onSave action state = state { onSaveCB = action }++onLoad :: Maybe LoadFun -> C -> C+onLoad action state = state { onLoadCB = action }++clearLabel :: C -> IO () +clearLabel state = labelSetText (View.statusL (gui state)) ""+ +-- internal affairs+data C = C + { gui :: View.ViewState+ , onSaveCB :: Maybe SaveFun+ , onLoadCB :: Maybe LoadFun+ , filesuffix :: Maybe String+ }++save :: C -> IO ()+save state = saveFileDialog "Save file " (maybe Nothing (Just . (++) "*.") (filesuffix state)) $ + do \fileName ->+ case onSaveCB state of+ Nothing -> + return ()+ Just callback ->+ do+ c <- callback+ case c of+ Nothing -> + return ()+ Just c' ->+ let realfn = maybe + fileName + (extendFileName fileName) + (filesuffix state)+ in do + L.writeFile realfn c'+ postGUIAsync $ labelSetText (View.statusL $ gui state) $ realfn ++ " Saved." + where+ extendFileName fileName suffix = if isSuffixOf suffix fileName + then fileName + else fileName ++ "." ++ suffix++load :: C -> IO ()+load state = openFileDialog "Open file" (maybe Nothing (Just . (++) "*.") (filesuffix state)) $ + do \fileName ->+ case onLoadCB state of+ Nothing -> + return ()+ Just callback ->+ do+ c <- L.readFile fileName+ callback c+ postGUIAsync $ labelSetText (View.statusL $ gui state) $ fileName ++ " Loaded."+ +{-+test = windowedApp "LoadSaveController test" $ do+ clut <- new $ Nothing+ mw <- View.mainWidget <$> (clut .> gui)+ clut .< onLoad (Just L.putStrLn)+ clut .< onSave (Just (return (Just (L.pack "this is a test"))))+ return mw+-}
+ LoadSaveView.hs view
@@ -0,0 +1,25 @@+module LoadSaveView where++import Graphics.UI.Gtk++data ViewState = ViewState + {+ mainWidget :: Widget+ , statusL :: Label+ , saveB :: Button+ , openB :: Button+ }++new :: IO ViewState+new = do+ l <- labelNew Nothing+ labelSetSelectable l True+ s <- buttonNewFromStock stockSave+ o <- buttonNewFromStock stockOpen++ box <- hBoxNew False 2+ boxPackStart box o PackNatural 2+ boxPackStart box s PackNatural 2+ boxPackEnd box l PackNatural 2++ return $ ViewState (toWidget box) l s o
+ MultiWidgetContainerController.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE ExistentialQuantification #-}++module MultiWidgetContainerController+ (+ new+ , test+ , view+ , Controller+ ) where++import Graphics.UI.Gtk+import qualified MultiWidgetContainerView as View+import qualified DataProcController as DP+import Component+import WindowedApp+import Control.Applicative+import qualified Data.Map as M++type Controller = Ref C++view = View.mainWidget . gui++new :: IO Controller+new = do+ v@(View.V _ _ _ ab) <- View.new + this <- newRef (C v M.empty)+ onClicked ab (this .<< addComponent) + return this++removeComponent :: String -> C -> C+removeComponent name state = + state { components = (M.delete name (components state)) }+ +addComponent :: C -> IO C+addComponent (C g cs) = do+ name <- entryGetText (View.nameE g)+ putStrLn $ "add comp " ++ name+ dpc <- DP.new+ (dpc .> DP.view) >>= View.add g name+ let cs' = M.insert name dpc cs+ return (C g cs')++-- internal +data C = C + { gui :: View.ViewState+ , components :: M.Map String DP.Controller+ }++test = windowedApp "RegexDefinitionController test" $ do+ clut <- new+ mw <- View.mainWidget <$> (clut .> gui) + return mw
+ MultiWidgetContainerView.hs view
@@ -0,0 +1,37 @@+module MultiWidgetContainerView where++import Graphics.UI.Gtk++data ViewState = V + { mainWidget :: Widget+ , notebook :: Notebook+ , nameE :: Entry+ , addB :: Button+ }++new :: IO ViewState+new = do+ vbox <- vBoxNew False 4+ nb <- notebookNew + set nb [ notebookScrollable := True+ , notebookEnablePopup := True+ , notebookTabPos := PosRight + ]+ hbox <- hBoxNew False 4 + nameE <- entryNew+ addB <- buttonNewFromStock stockAdd++ boxPackEnd hbox addB PackNatural 2+ boxPackEnd hbox nameE PackNatural 2+ boxPackStart vbox nb PackGrow 2+ sep <- hSeparatorNew+ boxPackStart vbox sep PackNatural 2+ boxPackStart vbox hbox PackNatural 2+ return (V (toWidget vbox) nb nameE addB)++add :: ViewState -> String -> Widget -> IO ()+add vs name w = do+ putStrLn $ "appending notebook page " ++ name+ notebookAppendPage (notebook vs) w name+ widgetShowAll (notebook vs) + return ()
+ PerformanceTuning.hs view
@@ -0,0 +1,41 @@+module Main where ++import qualified System.Process as P+import System.IO+import qualified Data.ByteString.Lazy.Char8 as L+import SimpleRegex+import Control.Concurrent++main = do+ lines <- L.readFile "testdata"+ res <- testAction lines+ putStrLn $ show res+ return ()+++interactive_test = do+ let text = "cat testdata"+ (hin, hout, _, ph) <- P.runInteractiveProcess "/bin/bash" [] Nothing Nothing+ hPutStr hin text+ lines <- L.hGetContents hout+ res <- testAction lines+ putStrLn $ show res+ ec <- P.waitForProcess ph + return ()++test_rx = "([0-9]{4}-[0-9]{2}-[0-9]{2} ..:..:..),....+[[][0-9]+[]] ([^ ]+) +(incoming|outgoing) c[Aa]ll #([0-9]+)/R:(.+)/C:(.+)/L:([0-9-]+) BCH#(.+)/DCH#(.+):(.+)/BRD#(.+) .+ CRN ([0-9A-Za-z]+) @([-0-9]+) (from|local) (.+) (to|remote) (.+) [[][^ ]+ *(.*)$"++testAction c = do+ rx <- compile "(incoming)"+ return $ map (submatches rx) (L.lines c)++testAction2 c = do+-- rx <- compile "([0-9]{4}-[0-9]{2}-[0-9]{2} ..:..:..),....+[[][0-9]+[]] ([^ ]+) +(incoming|outgoing) c[Aa]ll #([0-9]+)/R:(.+)/C:(.+)/L:([0-9-]+) BCH#(.+)/DCH#(.+):(.+)/BRD#(.+) .+ CRN ([0-9A-Za-z]+) @([-0-9]+) local (.+) remote (.+) [[][^ ]+ *(.*)$"+ rx <- compile "(incoming)"+ let sms = catSubmatches rx $ L.lines c+ return $ map (\ (ss, s) -> + case ss of+ [] -> [s]+ _ -> ss) sms++
+ README view
@@ -0,0 +1,6 @@++Tabloid is a gtk program that captures output from arbitrary shell commands in tables. +Each line can be separated with regular expressions. It aims to be somewhere between a +graphical awk and excel, although less powerful than both in every respect, it will +help analyze large logfiles. It allows outputs of diffrent programs/shellscripts +to be viewed directly.
+ RegexDefinitionController.hs view
@@ -0,0 +1,95 @@+module RegexDefinitionController+ ( + test+ , Controller+ , new+ , onUpdate+ , view+ ) where++import qualified Graphics.UI.Gtk.ModelView as MV +import qualified RegexDefinitionView as View+import qualified Data.ByteString.Lazy.Char8 as L+import SimpleRegex+import Component+import Graphics.UI.Gtk+import Data.IORef+import Data.Maybe+import Data.List+import Control.Monad+import Control.Applicative+import Control.Concurrent+import WindowedApp+import qualified LoadSaveController as LSC++type Controller = Ref C++view = View.mainWidget . gui++new = do+ lsc <- LSC.new (Just "regex")+ lscv <- (lsc .> LSC.view)+ v <- View.new lscv+ this <- newRef $ C Nothing v Nothing+ lsc .< (LSC.onLoad (Just (\cont -> + (this .<< (\state -> do+ entrySetText (View.regexE (gui state)) (L.unpack cont)+ onCh state)))))++ lsc .< (LSC.onSave (Just (this .>> (\state -> + Just <$> L.pack <$> (entryGetText $ View.regexE (gui state))))))+ + (View.regexE v) `onEntryActivate` (this .>> onAct)+ (View.regexE v) `onEditableChanged` (do + lsc .>> LSC.clearLabel+ this .<< onCh)+ (View.applyB v) `onClicked` (this .>> onAct)+ (View.applyB v) `widgetSetSensitivity` False+ return this++onUpdate :: Maybe ((Maybe (Regex,String)) -> IO ()) -> C -> C+onUpdate ma c = c { regexUpdateCB = ma }++-- internal main state record+data C = C+ { + regexUpdateCB :: Maybe (Maybe (Regex, String) -> IO ())+ , gui :: View.ViewState+ , currentRegex :: Maybe (Regex, String)+ }++-- internal callbacks and functions+onAct :: C -> IO ()+onAct c = do + case regexUpdateCB c of+ Just uf -> uf (currentRegex c)+ _ -> return ()++onCh :: C -> IO C+onCh c = do + str <- entryGetText $ View.regexE (gui c)+ res <- compileWithError str+ let v = gui c+ c' <- case res of + Right r -> + do+ labelSetText (View.errorL (gui c)) ""+ (View.applyB v) `widgetSetSensitivity` (str /= "")+ return (c {currentRegex = Just (r, str)})+ Left err -> + do+ labelSetText (View.errorL (gui c)) err + (View.applyB v) `widgetSetSensitivity` False+ return (c {currentRegex = Nothing})+ return c'++-- tests+test = windowedApp "RegexDefinitionController test" $ do+ clut <- new+ clut .< onUpdate $ Just (\r ->+ case r of+ Nothing -> putStrLn "not ok"+ Just (_,str) -> putStrLn $ "ok" ++ str)+ mw <- View.mainWidget <$> (clut .> gui)+ return mw+
+ RegexDefinitionView.hs view
@@ -0,0 +1,25 @@+module RegexDefinitionView where++import Graphics.UI.Gtk++data ViewState = ViewState+ {+ mainWidget :: Widget+ , regexE :: Entry+ , errorL :: Label+ , applyB :: Button+ }++new :: Widget -> IO ViewState+new openSaveW = do+ e <- entryNew + vbox <- vBoxNew False 2+ hbox <- hBoxNew False 2+ boxPackStart vbox e PackGrow 2+ boxPackStart vbox hbox PackNatural 2+ l <- labelNew Nothing+ applyB <- buttonNewFromStock stockApply+ boxPackStart hbox openSaveW PackNatural 2+ boxPackStart hbox l PackNatural 2+ boxPackEnd hbox applyB PackNatural 2+ return (ViewState (toWidget vbox) e l applyB)
+ ShellCmdInputController.hs view
@@ -0,0 +1,111 @@+module ShellCmdInputController+ (+ Controller+ , test+ , new+ , onUpdate+ , view+ , OnUpdate+ , runText+ ) where++import qualified Graphics.UI.Gtk.ModelView as MV +import qualified ShellCmdInputView as View+import Graphics.UI.Gtk+import Control.Monad+import Control.Applicative+import Control.Concurrent+import qualified System.Process as P+import System.IO+import WindowedApp+import Component+import qualified Data.ByteString.Lazy.Char8 as L+import SimpleRegex+import Control.Concurrent.MVar+import qualified LoadSaveController as LSC++type Controller = Ref C++view = View.mainWidget . gui++new = do+ buf <- textBufferNew Nothing+ lsc <- LSC.new (Just "sh")+ lscv <- (lsc .> LSC.view)+ v@(View.V _ runB _ _ _ _) <- View.new buf lscv+ lock <- newEmptyMVar+ this <- newRef (C v "" Nothing buf lock)+ runB `onClicked` (this .<< runText)+ lsc .< (LSC.onLoad (Just (\cont -> textBufferSetText buf (L.unpack cont))))+ lsc .< (LSC.onSave (Just (Just <$> L.pack <$> getBufferText buf))) + onBufferChanged buf (lsc .>> LSC.clearLabel)+ return this++type OnUpdate = L.ByteString -> IO ()++onUpdate :: Maybe OnUpdate -> C -> C+onUpdate cb state = state {executeCB = cb}+-- internal functions +data C = C + { + gui :: View.ViewState+ , currentCmd :: String+ , executeCB :: Maybe OnUpdate+ , buffer :: TextBuffer+ , execLock :: MVar Bool+ }++getBufferText buf = do+ s <- textBufferGetStartIter buf+ e <- textBufferGetEndIter buf+ textBufferGetText buf s e True+ +runText :: C -> IO C+runText state = do+ locked <- tryPutMVar (execLock state) True+ if locked then+ case executeCB state of+ Just execCB -> + do+ text <- getBufferText (buffer state)+ theshell <- entryGetText (View.shellE (gui state))+ forkOS $ executeCmd text execCB state (words theshell)+ return $ state+ Nothing -> + return state+ else return state++executeCmd text execCB state (theshell:theargs) = do+ postGUISync $ widgetSetSensitivity (View.cancelB $ gui state) True+ postGUISync $ widgetSetSensitivity (View.executeB $ gui state) False+ postGUISync $ widgetSetSensitivity (View.textView $ gui state) False+ postGUISync $ labelSetText (View.exitCodeL $ gui state) $ "Executing..."+ (hin, hout, _, ph) <- P.runInteractiveProcess theshell theargs Nothing Nothing+ postGUISync $ (View.cancelB $ gui state) `onClicked` (do+ P.terminateProcess ph+ return ())+ forkOS $ do+ hPutStr hin text+ lines <- L.hGetContents hout+ postGUISync $ execCB lines++ ec <- P.waitForProcess ph ++ postGUISync $ (View.cancelB $ gui state) `onClicked` (return ())+ postGUISync $ widgetSetSensitivity (View.cancelB $ gui state) False+ postGUISync $ widgetSetSensitivity (View.textView $ gui state) True+ postGUISync $ widgetSetSensitivity (View.executeB $ gui state) True+ postGUISync $ labelSetText (View.exitCodeL $ gui state) $ "Exit Code: " ++ show ec+ takeMVar (execLock state)+ return ()+ +-- tests+test = windowedApp "ShellCmdInputController test" $ do+ t <- new :: IO Controller+ t .< onUpdate (Just ((mapM_ putStrLn) + . (take 100) + . lines + . L.unpack))+ t .> view+ +
+ ShellCmdInputView.hs view
@@ -0,0 +1,68 @@+module ShellCmdInputView where++import Graphics.UI.Gtk+import Graphics.UI.Gtk.ModelView as MV++data ViewState = V + {+ mainWidget :: Widget+ , executeB :: Button+ , exitCodeL :: Label+ , textView :: TextView+ , cancelB :: Button+ , shellE :: Entry+ }++new buf fileButtons = do + cmdE <- textViewNewWithBuffer buf++ let stateTypes = [StateNormal, StateActive, StatePrelight, StateSelected, StateInsensitive]+ fgCols = [Color 65535 49152 0, Color 65535 49152 0, Color 65535 49152 0, Color 65535 49152 0]+ bgCols = [Color 255 192 0, Color 0 0 0, Color 0 0 0, Color 32535 32535 0]++ mapM_ (uncurry (widgetModifyText cmdE)) $+ stateTypes `zip` fgCols++ mapM_ (uncurry (widgetModifyBase cmdE)) $+ stateTypes `zip` bgCols++ scrollWin <- scrolledWindowNew Nothing Nothing+ set scrollWin + [ containerBorderWidth := 5+ , scrolledWindowShadowType := ShadowIn+ , scrolledWindowVscrollbarPolicy := PolicyAutomatic+ , scrolledWindowHscrollbarPolicy := PolicyAutomatic+ ]+ containerAdd scrollWin cmdE++ cmdBHbox <- hBoxNew False 2+ cmdRunB <- buttonNewFromStock stockExecute+ boxPackEnd cmdBHbox cmdRunB PackNatural 2+ + cancB <- buttonNewFromStock stockCancel+ widgetSetSensitivity cancB False+ boxPackEnd cmdBHbox cancB PackNatural 2+ + boxPackStart cmdBHbox fileButtons PackNatural 2++ exitL <- labelNew Nothing+ boxPackStart cmdBHbox exitL PackGrow 2++ shellH <- hBoxNew False 2+ shellL <- labelNew $ Just "Shell: "+ shellE <- entryNew+ entrySetText shellE "/bin/bash"+ boxPackStart shellH shellL PackNatural 2+ boxPackStart shellH shellE PackGrow 2++ centerHB <- hBoxNew False 2+ + cmdVbox <- vBoxNew False 2+ boxPackStart cmdVbox shellH PackNatural 2+ boxPackStart cmdVbox scrollWin PackGrow 2+ boxPackStart cmdVbox cmdBHbox PackNatural 2 ++ vbox <- vBoxNew False 2+ boxPackStart vbox cmdVbox PackGrow 2++ return $ V (toWidget vbox) cmdRunB exitL cmdE cancB shellE
+ SimpleRegex.hs view
@@ -0,0 +1,99 @@+module SimpleRegex + (+ Regex+ , submatches+ , compile+ , compileWithError+ , ByteString + , regexSplit+ , catSubmatches+ ) where++import qualified Text.Regex.Posix.ByteString.Lazy as R+import Data.ByteString.Lazy.Char8 as L hiding (putStrLn, map, take, concat, zipWith)+import Data.Either+import Data.Maybe+import Text.Regex.Base.RegexLike+import Control.Monad+import System.IO.Unsafe++type Regex = R.Regex++-- very simple matching functions for lazy bytestream regexp++submatches :: Regex -> ByteString -> Maybe [ByteString]+submatches r s = let m = unsafePerformIO (R.regexec r s) in+ case m of+ Right (Just (_,_,_,ms)) -> + Just ms+ _ -> + Nothing++catSubmatches :: Regex -> [ByteString] -> [([ByteString], ByteString)]+catSubmatches r ss = let sms = map (submatches r) ss+ in catMaybes $ zipWith (\le ri -> le >>= (\le' -> return (le', ri))) sms ss++regexSplit :: Regex -> ByteString -> [ByteString]+regexSplit r s = + let+ m = unsafePerformIO (R.regexec r s)+ in case m of+ Right (Just (bef,_,aft,_)) -> + if L.length bef > 0 + then (bef : regexSplit r aft) + else [aft]+ _ -> + [s]++compile :: String -> IO Regex+compile rx = do+ (Right r) <- R.compile defaultCompOpt defaultExecOpt $ pack rx+ return r++compileWithError :: String -> IO (Either String Regex)+compileWithError rx = do+ r <- R.compile defaultCompOpt defaultExecOpt $ pack rx+ return $ case r of+ (Right r') -> + Right r'+ (Left (rc, rs)) -> + Left ("Error: " ++ rs)+++-- tests+{-+test1 = do+ rx <- compile ";"+ let teststr = L.pack ";;123;3456;234;"+ res = regexSplit rx teststr+ putStrLn (show res)+ +test2 = do+ rx <- compile ";"+ let testlines = L.pack ";;123;3456;234;\na;;asd;;24;das"+ res = (map (regexSplit rx)) (L.lines testlines)+ putStrLn (show res)+ +test3 = do+ rx <- compile ";"+ let ff d = catSubmatches rx d+ res = ff (L.lines testdata)+ putStrLn (show (take 200 res))+ +test4 = do+ rx <- compile " ;"+ let ff d = map (regexSplit rx) d+ res = ff (L.lines testdata)+ putStrLn (take 200 (show res))+ +test5 = do+ rx <- compile ""+ let testlines = L.pack ";;123;3456;234;\na;;asd;;24;das"+ res = (map (regexSplit rx)) (L.lines testlines)+ putStrLn (show res)+ +testdata = L.unlines $ L.pack <$>+ [concat (map ((++ ";") . show) [x * 10 .. 9 + x * 10]) | + x <- [1..10]]++-}
+ TableController.hs view
@@ -0,0 +1,188 @@+module TableController+ (+ Controller+ , test+ , new+ , view+ , push + ) where+ +import qualified Data.ByteString.Lazy.Char8 as L+import qualified Graphics.UI.Gtk.ModelView as MV +import qualified LoadSaveController as LSC+import qualified TableView as View+import Graphics.UI.Gtk+import Data.Maybe+import Data.List+import SimpleRegex+import Control.Monad+import Control.Applicative+import WindowedApp+import Component++type Controller = Ref C++type StringList = MV.ListStore [L.ByteString]++view :: C -> Widget+view = View.mainWidget . gui++new :: IO Controller+new = do+ lsc <- LSC.new (Just "jira")+ lsv <- (lsc .> LSC.view)+ l <- MV.listStoreNew [] + v <- View.new l lsv+ lsc .< (LSC.onSave (Just (Just <$> toJira l)))+ onClicked (View.searchPB v) (searchTableBackward (View.searchE v) l (View.treeView v))+ onClicked (View.searchNB v) (searchTableForward (View.searchE v) l (View.treeView v))+ View.add_col (View.treeView v) l (L.unpack . (!! 0)) "Line" 0+ this <- newRef (C v l 1 Nothing)+ onToggled (View.groupB v) (this .<< updateGrouping)+ onValueSpinned (View.groupE v) (this .<< updateGrouping)+ return this++push this rows = + this .<< \s -> + case groupCol s of+ Nothing -> + pushRaw (zipWith (:) (map (L.pack . show) [1..]) rows) s+ Just (c, _) ->+ pushRaw (zipWith (:) (map (L.pack . show) [1..]) rows) (s {groupCol = Just (c, rows)})++-- internal functions+groupRows [] _ = []+groupRows (row:rows) col = row : sameGroup ++ groupRows rest col+ where cell = row !! col+ (sameGroup, rest) = partition ((== cell) . (!! col)) rows +++updateGrouping state@(C g listM mc gCol) = + do+ col <- spinButtonGetValueAsInt (View.groupE g)+ grouping <- toggleButtonGetActive (View.groupB g)+ if grouping then + do+ rows <- MV.listStoreToList listM + case gCol of+ Nothing -> + pushRaw rows (state { groupCol = Just (col, rows)})+ Just (ccol, crows) ->+ pushRaw crows (state { groupCol = Just (col, crows)})++ else+ case gCol of+ Nothing -> + return (state { groupCol = Nothing }) + Just (ccols, crows) ->+ pushRaw crows (state { groupCol = Nothing})+ ++searchTableBackward entry liststore treeview = do+ selI <- getSelectedRow treeview+ rows' <- MV.listStoreToList liststore+ let rows = reverse $ zip [0..] (take selI rows')+ searchRowRange rows entry treeview++searchTableForward entry liststore treeview = do+ selI <- getSelectedRow treeview+ rows' <- MV.listStoreToList liststore+ let rows = zip [(selI + 1) ..] (drop (selI + 1) rows')+ searchRowRange rows entry treeview++searchRowRange rows entry treeview = do+ searchTerm <- entryGetText entry+ let sI = filter (any (isInfixOf searchTerm) . (map L.unpack) . snd) rows+ case sI of+ ((nexti, _):_) -> do+ MV.treeViewSetCursor treeview [nexti] Nothing+ _ -> + beep++getSelectedRow treeV = do+ tsel <- MV.treeViewGetSelection treeV+ sel <- MV.treeSelectionGetSelectedRows tsel+ return $ case sel of+ ((fst_sel:_):_) -> fst_sel+ _ -> 0++{-+toCsv :: StringList -> IO L.ByteString +toCsv liststore = do+ cont <- MV.listStoreToList liststore+ return (L.unlines (lineToCsv <$> cont))+ where+ lineToCsv :: [L.ByteString] -> L.ByteString+ lineToCsv ls = + let els = doQuotes <$> ls+ in L.concat (intersperse (L.pack ";") els)+ doQuotes :: L.ByteString -> L.ByteString+ doQuotes cont = + let escaped = L.foldl' (\s c -> + s `L.append` + (if c == '"' + then L.pack ['\\', c] + else L.pack [c])) + L.empty+ cont+ in (L.pack "\"") `L.append` escaped `L.append` (L.pack "\"")+-}+toJira :: StringList -> IO L.ByteString +toJira liststore = do+ cont <- MV.listStoreToList liststore+ return (L.unlines (lineToCsv <$> cont))+ where+ lineToCsv :: [L.ByteString] -> L.ByteString+ lineToCsv ls = + let els = escape <$> ls+ in (L.pack "| ") `L.append` + L.concat (intersperse (L.pack " | ") els) `L.append` (L.pack " |")+ escape :: L.ByteString -> L.ByteString+ escape cont = L.foldl' (\s c -> + s `L.append` + (if c == '|' + then L.pack ['\\', c] + else L.pack [c])) + L.empty+ cont++pushRaw :: [[L.ByteString]] -> C -> IO C+pushRaw rows c@(C g listM mc groupCol) =+ let + addRows n [] = return n+ addRows n (r:rs) = do+ let r' = length r+ when (r' > n) $ forM_ [n .. r' - 1] + (\i -> + View.add_col (View.treeView g)+ listM (at i) ("$" ++ show i) i)+ (MV.listStoreAppend listM) r+ addRows (max n r') rs+ at :: Int -> [L.ByteString] -> String+ at i xs + | (length xs) <= i = ""+ | otherwise = L.unpack $ xs !! i+ in + do+ (MV.listStoreClear listM)+ rowcount <- spinButtonGetValueAsInt (View.maxLines g)+ let groupedRows = maybe selRows (groupRows selRows . fst) groupCol+ selRows = (take rowcount rows) + cs <- addRows mc groupedRows+ spinButtonSetRange (View.groupE g) 0 (fromIntegral (cs - 1))+ return (c { cols = cs })++data C = C + { + gui :: View.ViewState+ , listModel :: StringList+ , cols :: Int+ , groupCol :: Maybe (Int, [[L.ByteString]])+ }+++-- tests+test = windowedApp "TableController test" $ do+ t <- new :: IO Controller+ t `push` [[(L.pack . show) (c `rem` 3)| c <- [r .. r + 10]] | r <- [0 .. 9]] + t .> view
+ TableView.hs view
@@ -0,0 +1,86 @@+module TableView where++import Graphics.UI.Gtk+import Graphics.UI.Gtk.ModelView as MV+import qualified Data.ByteString.Lazy.Char8 as L++data ViewState = V + {+ mainWidget :: Widget+ , treeView :: MV.TreeView+ , maxLines :: SpinButton+ , searchE :: Entry+ , searchPB :: Button+ , searchNB :: Button+ , groupB :: ToggleButton+ , groupE :: SpinButton+ }++new treeM loadSaveW = do+ vbox <- vBoxNew False 2+ hbox <- hBoxNew False 2+ l <- labelNew (Just "Max. Lines: ")++ sl <- labelNew (Just "Search in Table: ")+ searchE <- entryNew+ searchNB <- buttonNewFromStock stockGoDown+ searchPB <- buttonNewFromStock stockGoUp++ maxLines <- spinButtonNewWithRange 1 50000 1+ spinButtonSetValue maxLines 5000.0 ++ groupL <- labelNew (Just "Column: ")+ groupE <- spinButtonNewWithRange 0 50000 1+ spinButtonSetWrap groupE True+ spinButtonSetSnapToTicks groupE True+ spinButtonSetValue groupE 0+ groupB <- toggleButtonNewWithLabel "Group"++ v1 <- vSeparatorNew+ v2 <- vSeparatorNew+ v3 <- vSeparatorNew++ boxPackStart hbox loadSaveW PackNatural 2 + boxPackStart hbox v1 PackNatural 2 + boxPackStart hbox sl PackNatural 2 + boxPackStart hbox searchE PackNatural 2 + boxPackStart hbox searchPB PackNatural 2 + boxPackStart hbox searchNB PackNatural 2 + boxPackStart hbox v2 PackNatural 2 + boxPackStart hbox groupL PackNatural 2 + boxPackStart hbox groupE PackNatural 2 + boxPackStart hbox groupB PackNatural 2 + boxPackStart hbox v3 PackNatural 2 + boxPackEnd hbox maxLines PackNatural 2 + boxPackEnd hbox l PackNatural 2++ treeV <- MV.treeViewNewWithModel treeM+ scrollWin <- scrolledWindowNew Nothing Nothing+ containerAdd scrollWin treeV++ boxPackStart vbox scrollWin PackGrow 2+ boxPackStart vbox hbox PackNatural 2++ set treeV [ MV.treeViewHeadersClickable := True+ , MV.treeViewHeadersVisible := True+-- , MV.treeViewEnableGridLines := MV.TreeViewGridLinesHorizontal + , MV.treeViewReorderable := True+ , MV.treeViewRulesHint := True+ , MV.treeViewEnableSearch := True+-- , MV.treeViewRubberBanding := True+ ]+ return $ V (toWidget vbox) treeV maxLines searchE searchPB searchNB groupB groupE++add_col treeV listM row_sel title scolid = do+ strCol <- MV.treeViewColumnNew+ strColRenderer <- MV.cellRendererTextNew+ set strColRenderer [ MV.cellTextEditable := True ]+ MV.treeViewColumnPackStart strCol strColRenderer True+ MV.treeViewColumnSetSortColumnId strCol scolid + MV.cellLayoutPackStart strCol strColRenderer True+ MV.treeViewColumnSetResizable strCol True+ MV.cellLayoutSetAttributes strCol strColRenderer listM $ \row -> + [MV.cellText := (row_sel row) ]+ MV.treeViewColumnSetTitle strCol title+ MV.treeViewAppendColumn treeV strCol+ return ()
+ WindowedApp.hs view
@@ -0,0 +1,22 @@+module WindowedApp + (+ windowedApp+ ) where+ +import Graphics.UI.Gtk++windowedApp :: String -> IO Widget -> IO ()+windowedApp title action = do+ unsafeInitGUIForThreadedRTS+ window <- windowNew+ mainWidget <- action+ set window [windowDefaultWidth := 800, + windowDefaultHeight := 600,+ containerBorderWidth := 10, + windowTitle := title,+ containerChild := mainWidget]++ onDestroy window mainQuit+ widgetShowAll window+ mainGUI+
tabloid.cabal view
@@ -1,5 +1,5 @@ Name: tabloid-Version: 0.2+Version: 0.3 Description: GUI for shell commands and log analysis Category: System Synopsis: View the output of shell commands in a table