Gabriella439 / turtle

Shell programming, Haskell style
BSD 3-Clause "New" or "Revised" License
943 stars 90 forks source link

Full practical example #10

Open codygman opened 9 years ago

codygman commented 9 years ago

Nothing like a full example to pull all the concepts you just learned together. I give you this code so you can see review the code (and see what mistakes a new user of your library made) and to possibly include for others to learn from.

I know some repositories have a examples subdirectory. This could even fit on a Tutorials.DatabaseBackup example documentation page or something?

code:

{-# LANGUAGE OverloadedStrings #-}
import           Control.Category ((<<<), (>>>))
import qualified Control.Foldl    as Fold
import           Data.Maybe
import           Turtle

run sink f = sink (f empty)

main = do
  -- backup old db
  output "revert.sql" (inproc "mysqldump" ["-uroot", "myproject"] empty)

  -- newest filename from server
  newestFileName <- fmap fromText . listToMaybe <$>
                    fold ((inproc "ssh" ["lalala", "ls -Art /backups/db/myproject | tail -n 1"]) empty) Fold.list
  let serverName = "lalala"
      dbName = "myproject-copy"
      backupDir = "/backups/db/myproject/"
      newestFileName' = fromMaybe (error "couldn't get backup path") newestFileName
      backupFilePath = backupDir </> newestFileName'
      localFilePath = "/tmp/" </> newestFileName'
      backupFilePath' = either (\e -> error $ "Couldn't parse backupFilePath: " <> show e) id (toText backupFilePath)
      localFilePath' = either (\e -> error $ "Couldn't parse localFilePath: " <> show e) id (toText localFilePath)

  -- download backup
  sh $ inproc "rsync" ["-avz", serverName, ":", backupFilePath', localFilePath'] empty

  -- drop old db (should have backup)
  run sh (inshell ("mysqladmin -uroot drop -f " <> dbName))

  -- restore database from just downloaded backup
  run sh (inshell ("zcat " <> localFilePath') >>> inshell ("mysql -uroot" <> dbName))

  print "done"
Gabriella439 commented 9 years ago

Not bad! A few comments:

Other than that, though, this is a really useful example. I will add this to an examples directory

codygman commented 9 years ago

Thanks for the feedback! I was going to try and use turtle alone to rewrite this:

https://github.com/andreafabrizi/Dropbox-Uploader/blob/master/dropShell.sh

But making a repl in Turtle wrapping bash seems weird and potentially difficult. The reason was I wanted to test replacing pieces of a shell library and seeing how it worked out.

I'm struggling to figure out what the Turtle equivalent of this shell code would be:

(source: https://github.com/andreafabrizi/Dropbox-Uploader/blob/master/dropShell.sh#L335)

while (true); do

    #Reading command from shell
    read -e -p "$username@Dropbox:$CWD$ " input

    #Tokenizing command
    eval tokens=($input)
    cmd=${tokens[0]}
    arg1=${tokens[1]}
    arg2=${tokens[2]}

    #Saving command in the history file
    history -s "$input"
    history -w "$SHELL_HISTORY"

    case $cmd in

        ls)
            sh_ls "$arg1"
        ;;

Mainly I'm not sure what input is or where it's coming from, though I'm guessing somewhere else in the script.

codygman commented 9 years ago

I'd like to (and think it would be fun to) convert the linux steam install script that famously deleted someone’s entire root directory because of an uninitialized environmental variable.

It would also be good PR for Turtle and even the "fiercest' pragmatists would acknowledge it solved a real world task. link if you're curious:

https://github.com/indrora/steam_latest/blob/master/scripts/steam.sh

Gabriella439 commented 9 years ago

I'm very familiar with that Steam bug :)

I can translate what the script is doing. read -e -p "$username@Dropbox:CWD$ " input is basically saying:

The equivalent of this in turtle would roughly be:

example userName shellHistory = forever (do
    Right dirTxt <- fmap toText pwd
    echo (format (s%"@Dropbox:"%s%"$ ") userName dirTxt)
    Just (input@[cmd, arg1, arg2]) <- readline
    proc "history" ["-s", input]
    proc "history" ["-w", shellHistory]
    case cmd of
        "ls" -> ... )

I skipped over proper error handling. A more robust script would handle errors with more descriptive error messages.

Also, it just occurred to me that it might be worth providing a Format specifier for FilePaths. In other words, something like this:

fp :: Format r (FilePath -> r)

So then you could just write:

            dir <- pwd
            echo (format (s%"@Dropbox:"%fp%"$ ") userName dir)
codygman commented 9 years ago

@Gabriel439 Just to make sure we aren't duplicating work, I wanted to let you know I'm working on a PR with examples with the same style as bos/wreq.

Gabriella439 commented 9 years ago

Alright

houshuang commented 9 years ago

Just wanted to +1 this. It would be great to have even more small stand-alone examples, possibly just a link to different gists on the wiki, or however people like to share their scripts.

juhp commented 8 years ago

+1 from me too: I am finding the learning curve bit steep (thinking in terms of shell).

It could also just be on the wiki or a separate git repo but having lots of different real examples would be most helpful to pick up the idioms.

juhp commented 7 years ago

Here is another misc example

(my first real use of turtle:- feedback very welcome)

Gabriella439 commented 7 years ago

@juhp: You can simplify your code a little bit like this:

checkPkg top p = do
  True <- testdir $ branchDir "epel7"
  True <- testfile $ specfile "epel7"
  arch <- rpmspecSrc "%{arch}" $ specfile_ "epel7"
  guard (arch == "noarch")
  el7 <- rpmspecSrc nvr (specfile_ "epel7")
  (_, out) <- procStrict "koji" ["latest-pkg", "--quiet", "epel7", p] empty
  guard (out /= "")
  cur:_ <- return (Data.Text.words out)
  guard (cur == el7 <> ".el7")
  True <- testfile $ specfile "f20"
  f20 <- rpmspecSrc nvr (specfile_ "f20")
  if (el7 < f20)
      then echo (el7 <> " < F20 " <> f20)
      else do
          f21 <- rpmspecSrc nvr (specfile_ "f21")
          guard (el7 < f21)
          echo (el7 <> "< F21 " <> f21)

Also, instead of:

main = sh $ do
  top <- pwd
  arguments >>= mapM_ (checkPkg top)

... you can do:

main = sh $ do
  top  <- pwd
  args <- arguments
  arg  <- select args
  checkPkg top arg

In fact, you could just inline the definition of checkPkg into your main after this change

Gabriella439 commented 7 years ago

Alright, so I polished up @codygman's original script and added it to a newly formed examples/ directory in this commit: 43a0f2371c5145ae8341cf5a32018f1e69230d46

I'll update this thread as I go

juhp commented 7 years ago

Thanks! :-)

Update: I put your suggestions into https://pagure.io/haskell-sig/blob/master/f/scripts/el7merge.hs