[personal profile] horrorcheck
Last week, I was feeling sort of frustrated and stuck. This week, I was volunteering at a summer camp, so I didn't have time to be stuck! I taught kids Scratch and pretended Minecraft is educational. It was fun. :D

I'm writing this from Hac Boston, a Haskell hackathon. I didn't realize that the hackathon would be more about hacking ON Haskell libraries, rather than making stuff WITH Haskell/Haskell libraries. It says "you don't even need any Haskell experience at all" on the event page but I should know by now that is always misleading. "You don't need any Haskell experience to attend -- but count on sitting at a table alone feeling dumb because you don't understand even the explanations that other attendees make obvious are super dumbed-down" would be more accurate.

So this weekend I learned that all the stereotypes about Haskell users are totally true, I was just protected from reality by the cool people at #nothaskell, Lambda Ladies, Recurse Center, and now TLC. :)

Despite this, this hackathon has helped me get unstuck on my KiSS set viewer project! I mentioned that I was trying to use Snap for my web framework, and several people noted that Snap was "over-engineered", so that made me feel better about not understanding it. I switched to Scotty and made immediate progress!

The website I'm making will to allow users to upload a KiSS set and play with it in the browser. Here's a sample set. KiSS sets are image files called "cels", color palettes, and a configuration file, all bundled into a compressed archive. So, my app needs to decompress the files, convert the cels with a palette, and parse the configuration file. Then it needs to serve up the set as HTML, CSS, and JavaScript.

I already had the conversion and parsing completed, so I just needed put it all on the web. That's what I'm working on this weekend!

I'm going to try to explain what I have so far so I can understand it:

main = scotty 3000 $ do
  middleware logStdoutDev
  middleware $ staticPolicy (noDots >-> addBase "static") 

This starts the server on localhost port 3000. "logStdoutDev" The "staticPolicy" part says where static files like images and stuff are. That's where uploaded set files are going to be.

get "/" $
    blaze Home.render

This is how you do routing in Scotty. When the user goes to (made up domain) "www.playsmooch.com/", then Scotty will use the "blaze" templating language to render this template "Home".

post "/upload" $ do
    fs <- files
    let file = case fs of 
                [(_, b)]  -> (BS.unpack (fileName b), fileContent b)
                otherwise -> error "Must upload exactly one file."
    let staticDir = "static/sets/" ++ takeBaseName (fst file)
    let relDir = "sets/" ++ takeBaseName (fst file)
    liftIO $ B.writeFile ("static/sets/" ++ fst fs') (snd file)
    liftIO $ unzipFile (fst file) staticDir
    cels <- liftIO $ convertSet (fst file) staticDir
    blaze $ TemplateKissSet.render relDir cels

When you hit the "Upload" button, you get sent to "www.playsmooch.com/upload", allll this stuff happens, and then it displays the set. I basically copied it from this example and then tweaked it.

Anyway, this what it does line-by-line:

post "/upload" $ do

So this says Scotty will do the following when it gets a POST request to "/upload".

fs <- files

"files" is a Scotty function that gets any files that are attached to the POST request.

let file = case fs of 
    [(_, b)]  -> (BS.unpack (fileName b), fileContent b)
    otherwise -> error "Must upload exactly one file."

This looks at the list of files and makes sure it makes sense. If the user does what I expect and chooses exactly one file, then fs is going to be a list with one element. It'll look something like [(fileName, fileContents)], and I can work with that. But if the user tries to upload more than one file or doesn't choose anything, then I have no idea what to do so I'm throwing an error.

let staticDir = "static/sets/" ++ takeBaseName (fst file)
let relDir = "sets/" ++ takeBaseName (fst file)

It's really useful to have these to pass around. staticDir is where the sets are compared to the source files. relDir is where the sets are compared to the static directory.

liftIO $ B.writeFile ("static/sets/" ++ fst file) (snd file)

"file" looks like (fileName, fileContent). So "snd file" is the contents of the file. writeFile writes those contents to the directory "static/sets" with the name fileName. writeFile has the type IO, so we're going to use liftIO to use IO in Scotty.

liftIO $ unzipFile (fst file) staticDir
cels <- liftIO $ convertSet (fst file) staticDir

unzipFile is a function I made to, you guessed it, unzip the compressed file (fst file) to the staticDir. convertSet converts all the cels and the cnf and really does waaay to much, but anyway, it returns a list of cels.

blaze $ KissSet.render relDir cels

Finally, I display the set by sending the list of cels and directory to a template.

I'm not going to go into "unzipFile" and "convertSet" or the templates because this post is already waaaaaaaay too long. But I do want to say that I really like Scotty A LOT and it's much easier to understand than Snap.


Right now I get 500 responses whenever something doesn't work, which I don't like. I want to have nice errors for stuff like, users trying to upload the wrong type of file, or the file being corrupted, or the parser not understanding the configuration file (my parser still needs work), or or or... There's lots of points of failure here that I'm not handling. What's the right way to do that?

Next week

I'm going to keep working on this and add lots of tests. One thing I found out while working on this is that since I'm doing stuff that Haskell doesn't really expect me to do, the type system isn't helping me find as many bugs. Instead I'm finding them during runtime, which is no fun. So, tests!
Anonymous( )Anonymous This account has disabled anonymous posting.
OpenID( )OpenID You can comment on this post while signed in with an account from many other sites, once you have confirmed your email address. Sign in using OpenID.
Account name:
If you don't have an account you can create one now.
HTML doesn't work in the subject.


Notice: This account is set to log the IP addresses of everyone who comments.
Links will be displayed as unclickable URLs to help prevent spam.



August 2015


Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Oct. 23rd, 2017 04:23 am
Powered by Dreamwidth Studios