deft/pres-Turtle.org
Yann Esposito (Yogsototh) 0199b269b8
presentation turtle
2018-11-07 23:02:02 +01:00

11 KiB

Shell scripts in Haskell & turtle?

Script shell en Haskell: Pourquoi?

Description

turtle is a reimplementation of the Unix command line environment in Haskell so that you can use Haskell as both a shell and a scripting language.

Features include:

  • Batteries included: Command an extended suite of predefined utilities
  • Interoperability: You can still run external shell commands
  • Portability: Works on Windows, OS X, and Linux
  • Exception safety: Safely acquire and release resources
  • Streaming: Transform or fold command output in constant space
  • Patterns: Use typed regular expressions that can parse structured values
  • Formatting: Type-safe printf-style text formatting
  • Modern: Supports text and system-filepath

Pourquoi?

  • Facile à maintenir, à refactorer (fp + types)
  • programme type checké et interprété rapidement (<1s)
  • syntaxe "légère", distinction string, variables, etc..
  • scripts shell sont seulement interprétés et lents; scripts Haskell peuvent être interpretés & lent ou compilés & rapide

Parfait pour remplacer de gros (> 100 lignes) et lents scripts bash.

Shell: Good

  1. commandes internes
  2. commandes externes
  3. composition:

    • redirection de la sortie standard
    • pipe
    • async

Shell: BAD

  1. variables (syntaxe $FOO, $foo, etc…)
  2. gestion des string (for i in *; do …; done) (files with ' ' or "\n", etc…)
  3. modularité du code, fonctions
  4. Vitesse d'exécution

Haskell Turtle

Commandes Internes

Turtle.Prelude

  • cd, ls, find, pwd, chmod, etc…
  • sleep, echo, err, arguments, realpath
  • mv, mkdir, mktree (mkdir -p)
  • cp, cptree, symlink
  • date, datefile
  • which, whichAll (tous les PATHs)

commandes externes

  • beaucoups de variantes plus ou moins "safes", "génériques"
inshell :: Text -- ^ Command Line
        -> Shell Line -- ^ Lines of stdin
        -> Shell Line -- ^ Lines of stdout
  • variantes avec separation des arguments shell vs proc
  • variantes strictes (stream)
  • avec sans, stderr
  • avec sans gestion des valeurs de retour

commandes externes (2)

La forme la plus générique:

procStrictWithErr :: MonadIO io
  => Text       -- ^ Command
  -> [Text]     -- ^ Arguments
  -> Shell Line -- ^ Lines of standard input
  -> io (ExitCode, Text, Text) -- ^ (Exit code, stdout, stderr)

Run a command using execvp, retrieving the exit code, stdout, and stderr as a non-lazy blob of Text

composition: redirections

cmd < input.txt > output.txt

Notion de stream de valeurs, generalement: Shell Line

-- wc -l <input.txt >output.txt
inshell "wc -l" (input "input.txt") (output "output.txt")

composition: pipes

Bash:

ls | grep foo

Turtle:

ls & grep "foo"

composition: async

Bash:

sleep 1; echo "foo" &

Turtle:

fork (do {shell "sleep 1" empty; echo "foo"})

composition: async

>>> :set +m
>>> runManaged $ do
  fork $ do
     echo "start"
     sleep 1
     echo "foo"
     sleep 2
     echo "done"
  echo "waiting a bit"
  sleep 2
  echo "stop waiting"
waitsitnagr ta
 bit
foo
stop waiting

Securité des resources Managed

  • take a resource and think to free it:

    • readonly
    • writeonly
    • appendonly
  • mktemp create a temporary file, handle race condition
  • fork, wait
  • pushd; view (pushd "/tmp" >> "pwd") (> =FilePath "/tmp") pwd (FilePath "/")

Fold

Foldl pour faire des accumulations efficacement (plus vite qu'en C) et en un seul parcours.

>>> fold (ls "/tmp") ((,,) <$> Fold.length <*> Fold.head <*> Fold.last)
(21,Just (FilePath "/tmp/aaa"),Just (FilePath "/tmp/zzz"))

Gestion des arguments

Example (1/2)

options :: MonadIO io => Description -> Parser a -> io a

parser :: Parser (Text, Int)
parser = (,) <$> optText "name" 'n' "Your first name"
             <*> optInt  "age"  'a' "Your current age"

main = do
    (name, age) <- options "Greeting script" parser
    echo (repr (format ("Hello there, "%s) name))
    echo (repr (format ("You are "%d%" years old") age))

Example (2/2)

> ./options --name John --age 42
Hello there, John
You are 42 years old
> ./options --help
Greeting script

Usage: options (-n|--name NAME) (-a|--age AGE)

Available options:
 -h,--help                Show this help text
 --name NAME              Your first name
 --age AGE                Your current age

Real World™ Complex Example (1/4)

gpm :: IO ()
gpm = do
  subcmd <- options "Git Project Manager" parser
  case subcmd of
   Init              -> Init.init
   NewIssue issueOpt -> inGPM (Issue.handleNewIssue issueOpt)
   Review reviewCmd  -> inGPM (Review.handleReview reviewCmd)
   Serve serveCmd    -> Serve.handleServe serveCmd
   Hooks hooksCmd    -> inGPM (Hooks.handleHooks hooksCmd)

data Command = Init
             | NewIssue Issue.IssueOptions
             | Review Review.ReviewCommand
             | Serve Serve.ServeCommand
             | Hooks Hooks.HooksCommand

parser :: Parser Command
parser = subcommand "init" "Initialize gpm" (pure Init)
         <|> NewIssue <$> subcommand "new-issue"
                                     "Create a new Issue"
                                     Issue.parseIssueOptions
         <|> Review <$> subcommand "review"
                         "Review (use current branch by default)"
                         Review.parseReviewCmd
         <|> Serve <$> subcommand "serve"
                         "Serve the git to the web"
                         Serve.parseServeCommand
         <|> Hooks <$> subcommand "hooks"
                         "Handle hooks for this git repository"
                         Hooks.parseHooksCommand

Real World™ Complex Example (2/4)

> gpm
Git Project Manager

Usage: gpm (init | new-issue | review | serve | hooks)

Available options:
  -h,--help                Show this help text

Available commands:
  init                     Initialize gpm
  new-issue                Create a new Issue
  review                   Review (use current branch by default)
  serve                    Serve the git to the web
  hooks                    Handle hooks for this git repository

Real World™ Complex Example (3/4)

data IssueOptions = IssueOptions
                    { interactive :: Bool
                    , newIssue    :: NewIssue
                    }


parseIssueOptions :: Parser IssueOptions
parseIssueOptions = IssueOptions
                    <$> switch "interactive" 'i' "Interactive mode"
                    <*> parseNewIssue

parseNewIssue :: Parser NewIssue
parseNewIssue = do
  isPriority    <- optional $ optText "priority"  'p' "Priority A,B,C"
  isStatus      <- optional $ optText "status"    's' "The status of the issue (TODO, QUESTION, ...)"
  isTitle       <- optional $ optText "title"     't' "The status title"
  isUser        <- optional $ optText "creator"   'c' "The user that created the issue"
  isBranch      <- optional $ optText "branch"    'b' "The branch related to the issue"
  isTags        <- optional $ optText "tags"      'g' "comma separated tags"
  isAssignee    <- optional $ optText "assignee"  'a' "Assignee"
  isReviewers   <- optional $ optText "reviewers" 'r' "comma separated reviewers"
  isDescription <- optional $ optText "descr"     'd' "Long issue description"
  pure NewIssue { priority = maybe PriorityB toPriority isPriority
                , status = fromMaybe "TODO" isStatus
                , title  = fromMaybe "Issue Title" isTitle
                , user        = isUser
                , branch      = isBranch
                , tags        = maybe [] (T.splitOn ",") isTags
                , assignee    = isAssignee
                , reviewers   = maybe [] (T.splitOn ",") isReviewers
                , description = isDescription
                }

Real World™ Complex Example (4/4)

> gpm new-issue --help
Usage: gpm new-issue [-i|--interactive] [-p|--priority PRIORITY]
                     [-s|--status STATUS] [-t|--title TITLE]
                     [-c|--creator CREATOR] [-b|--branch BRANCH]
                     [-g|--tags TAGS] [-a|--assignee ASSIGNEE]
                     [-r|--reviewers REVIEWERS] [-d|--descr DESCR]
  Create a new Issue

Available options:
  -i,--interactive         Interactive mode
  -p,--priority PRIORITY   Priority A,B,C
  -s,--status STATUS       The status of the issue (TODO, QUESTION, ...)
  -t,--title TITLE         The status title
  -c,--creator CREATOR     The user that created the issue
  -b,--branch BRANCH       The branch related to the issue
  -g,--tags TAGS           comma separated tags
  -a,--assignee ASSIGNEE   Assignee
  -r,--reviewers REVIEWERS comma separated reviewers
  -d,--descr DESCR         Long issue description
  -h,--help                Show this help text

Regex? NO; Parsers!

match

>>> match ("can" <|> "cat") "cat"
["cat"]

Parser:

("can" <|> "cat")

Parser composition

Pattern a: Polymorphique a

  • Functor
  • Applicative (*>)
  • Monad
  • Alternative (<|>)
  • MonadPlus
  • Monoid a => Monoid (Pattern a)
  • Monoid a => SemiGroup (Pattern a)
  • Monoid a > Num (Pattern a); =+ (<=> <|>), * (<=> <>)

sed: Pattern Text

sed :: Pattern Text -> Shell Line -> Shell Line
>>> stdout (input "file.txt")
Test
ABC
42
>>> -- sed 's/C/D/g' file.txt
>>> stdout (sed ("C" *> return "D") (input "file.txt"))
Test
ABD
42

sed: Apply fn on matches

>>> -- sed 's/[[:digit:]]/!/g' file.txt
>>> stdout (sed (digit *> return "!") (input "file.txt"))
Test
ABC
!!
>>> import qualified Data.Text as Text
>>> -- rev file.txt
>>> stdout (sed (fmap Text.reverse (plus dot)) (input "file.txt"))
tseT
CBA
24

Helpers

  • inplace: perl -pi -e '...' file
  • find :: Pattern a -> FilePath -> Shell FilePath
  • findtree :: Pattern a -> Shell FilePath -> Shell FilePath (ie ls | grep)

Conclusion

SAFE - FAST - AWESOME -> CONFIDENCE

  • Batteries included: Command an extended suite of predefined utilities
  • Interoperability: You can still run external shell commands
  • Portability: Works on Windows, OS X, and Linux
  • Exception safety: Safely acquire and release resources
  • Streaming: Transform or fold command output in constant space
  • Patterns: Use typed regular expressions that can parse structured values
  • Formatting: Type-safe printf-style text formatting
  • Modern: Supports text and system-filepath