ssadler / hawk

Awk for Hoodlums
BSD 3-Clause "New" or "Revised" License
35 stars 2 forks source link

example use case #57

Closed gelisam closed 11 years ago

gelisam commented 11 years ago

Today at work I wanted to use hawk to solve the following problem, but I gave up. This issue is here to remind me to try that use case again at home and figure out whether it is hawk or me who was doing things wrong.

I had a file of the form

http://example.com

param1=foo
param2=bar

subparams=
  subparam1=foo
  subparam2=bar

param3=baz

which I wanted to transform into

http://example.com?param1=foo&param2=bar&subparams=subparam1%3Dfoo%26subparam2%3Dbar&param3=baz

Where the indented subparams are joined with '&' before being url-encoded.

I eventually solved the problem with the pipeline

sed 's/^  \(.*\)=\(.*\)/%26\1%3D\2/g' |
  grep -v '^$' |
  tr '\n' '&' |
  sed 's/&/?/' |
  sed 's/&%26/%26/g' |
  sed 's/=%26/=/g' |
  sed 's/&$//g'

Which required a lot of trial and errors. I would like instead to solve it by adding something like the following function to the prelude and calling hawk -l url:

import qualified Data.ByteString.Lazy as B
import Data.Function
import Data.List
import Data.Monoid

url (domain:xs) = domain <> "?"
   <> B.intercalate "&" rows
   <> "&"
   <> B.intercalate "%26" params'
   <> "&"
   <> B.intercalate "&" rows'
  where
    [rows, params, rows'] = groupBy ((==) `on` indented) xs
    indented x = B.take 2 x == "  "
    params' = B.replace "=" "%3D" $ map B.drop 2 $ params

But after much more trial-and-errors than it took me to implement the sed pipeline, I couldn't get the above definition to pass the typechecker.

gelisam commented 11 years ago

Ah, I was getting errors because I wasn't using OverloadedStrings, which I thought we were turning on by default. We are, but only for the user expression, not for the prelude! Which makes sense.

I tried to add it to my prelude, got an error, fixed a minor bug which was causing hawk to choke on pragmas, and now I'm trying to implement url again.

gelisam commented 11 years ago

My final hawk prelude is quite a bit longer than the sed solution, but at least now I have more experience creating a non-trivial prelude and thus a better understanding of the issues users might have using it. Closing.

{-# OPTIONS -XOverloadedStrings #-}
import           Prelude
import qualified Data.ByteString.Lazy.Char8 as B
import           Data.ByteString.Lazy.Char8 (ByteString)
import           Data.Monoid
import           Network.HTTP.Base

-- process the indented lines first, e.g.
--     > cat example.in
--     foo
--       bar
--         baz
--       quux
--     > cat example.in | hawk -l 'postorder (\x xs -> x <> "(" <> B.intercalate "," xs <> ")")'
--     foo(bar(baz()),quux())
postorder :: (ByteString -> [a] -> a) -> [ByteString] -> [a]
postorder call [] = []
postorder call (f:xs) = call f' ys
                      : postorder call xs'
  where
    n = indent f
    f' = B.drop n f
    ys = postorder call block

    indent = B.length . B.takeWhile (==' ')
    not_indented x = indent x <= n
    (block, xs') = break not_indented xs

url :: [ByteString] -> ByteString
url (domain:xs) = domain <> "?" <> args xs
  where
    args :: [ByteString] -> ByteString
    args = B.intercalate "&" . postorder encode . filter (B.any (/=' '))

    encode :: ByteString -> [ByteString] -> ByteString
    encode key = (key <>) . urlEncodeB . B.intercalate "&"

    urlEncodeB :: ByteString -> ByteString
    urlEncodeB = B.pack . urlEncode . B.unpack