presentation turtle

This commit is contained in:
Yann Esposito (Yogsototh) 2018-11-07 23:02:02 +01:00
parent 18cdccbb1c
commit 0199b269b8
Signed by untrusted user who does not match committer: yogsototh
GPG key ID: 7B19A4C650D59646

406
pres-Turtle.org Normal file
View file

@ -0,0 +1,406 @@
#+Title: Shell scripts in Haskell & turtle?
#+Author: Yann Esposito
#+Date: <2018-11-07 Wed>
* Script shell en Haskell: Pourquoi?
*** Description
#+BEGIN_QUOTE
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
#+END_QUOTE
*** 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"
#+BEGIN_SRC haskell
inshell :: Text -- ^ Command Line
-> Shell Line -- ^ Lines of stdin
-> Shell Line -- ^ Lines of stdout
#+END_SRC
- 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:
#+BEGIN_SRC haskell
procStrictWithErr :: MonadIO io
=> Text -- ^ Command
-> [Text] -- ^ Arguments
-> Shell Line -- ^ Lines of standard input
-> io (ExitCode, Text, Text) -- ^ (Exit code, stdout, stderr)
#+END_SRC
Run a command using execvp, retrieving the exit code, stdout, and stderr as a
non-lazy blob of Text
*** composition: redirections
#+BEGIN_SRC bash
cmd < input.txt > output.txt
#+END_SRC
Notion de stream de valeurs, generalement: =Shell Line=
#+BEGIN_SRC haskell
-- wc -l <input.txt >output.txt
inshell "wc -l" (input "input.txt") (output "output.txt")
#+END_SRC
*** composition: pipes
Bash:
#+BEGIN_SRC bash
ls | grep foo
#+END_SRC
Turtle:
#+BEGIN_SRC haskell
ls & grep "foo"
#+END_SRC
*** composition: async
Bash:
#+BEGIN_SRC bash
sleep 1; echo "foo" &
#+END_SRC
Turtle:
#+BEGIN_SRC haskell
fork (do {shell "sleep 1" empty; echo "foo"})
#+END_SRC
*** composition: async
#+BEGIN_SRC haskell
>>> :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
#+END_SRC
*** 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.
#+BEGIN_SRC haskell
>>> fold (ls "/tmp") ((,,) <$> Fold.length <*> Fold.head <*> Fold.last)
(21,Just (FilePath "/tmp/aaa"),Just (FilePath "/tmp/zzz"))
#+END_SRC
** Gestion des arguments
*** Example (1/2)
=options :: MonadIO io => Description -> Parser a -> io a=
#+BEGIN_SRC haskell
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))
#+END_SRC
*** Example (2/2)
#+BEGIN_SRC
> ./options --name John --age 42
Hello there, John
You are 42 years old
#+END_SRC
#+BEGIN_SRC
> ./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
#+END_SRC
*** Real World™ Complex Example (1/4)
#+BEGIN_SRC haskell
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
#+END_SRC
*** Real World™ Complex Example (2/4)
#+BEGIN_SRC bash
> 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
#+END_SRC
*** Real World™ Complex Example (3/4)
#+BEGIN_SRC haskell
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
}
#+END_SRC
*** Real World™ Complex Example (4/4)
#+BEGIN_SRC text
> 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
#+END_SRC
** Regex? NO; Parsers!
*** match
#+BEGIN_SRC haskell
>>> match ("can" <|> "cat") "cat"
["cat"]
#+END_SRC
Parser:
#+BEGIN_SRC haskell
("can" <|> "cat")
#+END_SRC
*** 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
#+BEGIN_SRC haskell
sed :: Pattern Text -> Shell Line -> Shell Line
#+END_SRC
#+BEGIN_SRC haskell
>>> 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
#+END_SRC
*** sed: Apply fn on matches
#+BEGIN_SRC haskell
>>> -- 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
#+END_SRC
*** 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