Haskell Army

>>> Commando and Conscript



Created by Lyndon Maydwell (maydwell@gmail.com)

Presentation: http://sordina.github.io/mfug_army (presented with reveal.js)

Github: https://github.com/sordina/mfug_army

What's all this then?



Just writing some real-world code in Haskell

Nothing drastic

But what did you done?

I wanted to create something similar to Watch [1],

with advanced features like Ruby's Guard [2] ...

but without any language dependencies. Just plain binaries...

decoupled into cooperating programs.

  1. http://en.wikipedia.org/wiki/Watch_(Unix)
  2. https://github.com/guard/guard
~/Documents/Code echo {Conscript,Commando}/*.hs \
| xargs -n 1 echo \
| grep -v Setup \
| xargs cat
| wc -l

145

The UNIX Philosophy

http://en.wikipedia.org/wiki/Unix_philosophy

  • Small is beautiful.
  • Make each program do one thing well.
  • Build a prototype as soon as possible.
  • Choose portability over efficiency.
  • Store data in flat text files.
  • Use software leverage to your advantage.
  • Use shell scripts to leverage and port.
  • Avoid captive user interfaces.
  • Make every program a filter.

Commando

github.com/sordina/Commando

What is Commando?

  • Watch a directory
  • Perform an action when files change
  • Simple right?

Commando's Arsenal


				build-depends:
					base,
					text,
					system-filepath,
					optparse-applicative,
					system-fileio,
					fsnotify >= 0.0.13,
					process
						

FSNotify

github.com/mdittmer/hfsnotify


  • Cross-Platform filesystem-notification project
  • Developed as a Google Summer of Code Project

Optparse-Applicative

github.com/pcapriotti/optparse-applicative


  • Composable (command-line) options parsing library
  • Applicative interface
  • Pure and impure top-level interfaces(!)

Conscript

github.com/sordina/Conscript

What is Conscript?

  • Run a command
  • (Re)start the command on lines of STDIN
  • Simple right?

Conscript's Rifle


				build-depends:
					base ==4.6.*,
					process ==1.1.*
						

Neat!

Now show me the codez!

Optparse

Applicative

Parser Definition

import Options.Applicative
import Data.Monoid ((<>))

quietFlag = short 'q'
quietHelp = help "silence the output"
quietOption = switch ( quietFlag <> quietHelp )

data Options = Opts {quiet :: Bool}

optParser :: Parser Options
optParser = Opts <$> quietOption

Pure Usage

      import Options.Applicative
      import Data.Monoid (mempty)
      import Options.Applicative.Types (ParserPrefs)

      data Options = Opts {quiet :: Bool} deriving Show

      pureParser :: (Parser a) -> [String] -> Maybe a
      pureParser parser = eitherToMaybe . execParserPure preferences information
        where information   = (info parser mempty)
              preferences   = prefs mempty
              eitherToMaybe = either (const Nothing) Just

      main :: IO ()
      main = do print $ pureParser optParser (words "-q")
                print $ pureParser optParser (words "I do what I want!")
									

Impure Usage

main :: IO ()
main = execParser
   (info (helper <*> options) mempty)
   >>= start

start :: Options -> IO ()
start = ...

Any freebies?

lyndon@endpin ~ commando --help
Usage: commando [COMMAND] [-q|--quiet] [-c|--consumer] [-i|--stdin] [-p|--persist] [DIRECTORY]

Available options:
   -h,--help Show this help text
   COMMAND Command run on events
   -q,--quiet Hide non-essential output
   -c,--consumer Pass events as argument to command
   -i,--stdin Pipe events to command
   -p,--persist Pipe events to persistent command
   DIRECTORY Directory to monitor

Controlling

Processes

Blocking effectively with MVars

              
          starter :: [String] -> MVar () -> MVar ProcessHandle -> IO ()
          starter args blocker running = do
            putMVar blocker () -- ENSURE WE ARE READY TO ROLL
            p <- startProcess args
            putMVar running p
            code <- waitForProcess p
            case code of ExitFailure 15 -> return () -- Killed!
                         ExitFailure i  -> putStrLn "DOH! [" ++ show i ++ "]"
                         ExitSuccess    -> return ()
              
            

Smooth Sailing?

Not exactly...


  • Many issues with fsnotify
    ... once I figured out which library was the right one ...
  • Line buffering... FML
    commando -c echo | grep --line-buffered Add | conscript ls
    Remember!!!
  • No ideal interface to process-management
    Runtime errors when handle operations are performed on the wrong stream...

TODO

More Options!


  • Only emit certain events
  • Determine what to spawn using input

TODO

Fix Bugs!


  • Removal events seem to only show pre-existing files
  • Handle errors more noisily

TODO

More Utilities!


  • Checksum utility to check for legit changes ( Medic? )
  • Throttle utility to discard spammed events ( Sapper? )
  • Handy composition of existing utilities ( Admiral? )

TODO

Library Interface!


  • WTF is the Category of Products of Categories??
  • Pipes Interface Instread?

Questions?