Skip to content
Browse files

Gittin' it up

  • Loading branch information...
0 parents commit 3a0a32e0a8907f087ce067c844d5be98d4af3c06 Jonas Westerlund committed
Showing with 640 additions and 0 deletions.
  1. +68 −0 Eval.hs
  2. +88 −0 IRC.hs
  3. +77 −0 L.hs
  4. +30 −0 LICENSE
  5. +41 −0 Main.hs
  6. +63 −0 Parser.hs
  7. +139 −0 Seen.hs
  8. +2 −0 Setup.hs
  9. +16 −0 TODO
  10. +40 −0 Types.hs
  11. +18 −0 Utils.hs
  12. +58 −0 norby.cabal
68 Eval.hs
@@ -0,0 +1,68 @@
+module Eval where
+
+import Control.Monad.Reader
+import Data.Char
+import Data.List
+
+import Mueval.ArgsParse
+import Mueval.Interpreter
+import Mueval.Parallel
+
+import qualified Language.Haskell.Interpreter as I
+
+import System.IO.Error
+import System.Process
+import Types
+import Utils
+
+hsFile = "L"
+
+-- Call out to the mueval binary
+evalHsExt :: Message -> IO String
+evalHsExt (Message _ _ params) = do
+ (_, out, _) <- liftIO $ readProcessWithExitCode "mueval" args ""
+ return $ " " ++ (unwords $ words out)
+ where args = ["-XExtendedDefaultRules",
+ "-XUnicodeSyntax",
+ "--noimports",
+ "-l", hsFile ++ ".hs",
+ "--expression=" ++ (drop 2 . last) params,
+ "-t30",
+ "+RTS", "-N2", "-RTS"]
+
+-- Evaluate a Haskell expression
+evalHs :: String -> IO String
+evalHs expr = do
+ r <- liftIO . I.runInterpreter . interpreter $ muOptions { expression = expr }
+ case r of
+ Left (I.WontCompile errs) -> return $ niceErrors errs
+ Left err -> error $ intercalate " " . lines $ show err
+ -- Expr, type, and result
+ Right (_, _, r) -> return $ " " ++ r
+
+ where muOptions = (getOptions []) { expression = expr,
+ loadFile = hsFile,
+ timeLimit = 5 }
+
+-- Get inferred type of an expression
+typeOf :: Message -> IO String
+typeOf (Message _ _ params) = do
+ -- ".type expr" -> "expr"
+ let expr = drop 6 $ last params
+ t <- liftIO . I.runInterpreter $ I.loadModules [hsFile]
+ >> I.setTopLevelModules [hsFile]
+ >> I.setImports ["Prelude"]
+ >> I.typeOf expr
+ case t of
+ Left (I.WontCompile errs) -> return $ niceErrors errs
+ Left err -> return $ show err
+ Right val -> return val
+
+niceErrors = excerpt' . intercalate " " . concatMap lines . fmap I.errMsg
+
+-- Pointfree refactoring
+pointFree :: Message -> IO String
+pointFree (Message _ _ params) = do
+ let expr = trim . dropWhile (not . isSpace) $ last params
+ (_, out, _) <- liftIO $ readProcessWithExitCode "pointfree" [expr] ""
+ return . intercalate " " $ lines out
88 IRC.hs
@@ -0,0 +1,88 @@
+module IRC (
+ module Parser,
+ Net,
+ connect,
+ listen,
+ privmsg,
+ socket,
+ write
+) where
+
+import Control.OldException (bracket_)
+import Control.Monad
+import Control.Monad.Reader hiding (join)
+import Data.Either
+import Data.List
+import qualified Eval as E
+import Network
+import Parser
+import qualified Seen as S
+import System.Exit
+import System.IO
+import Text.ParserCombinators.Parsec hiding (letter)
+import Types
+
+import qualified Utils as U
+
+-- A wrapper over IO, holding the bot's immutable state
+type Net = ReaderT Bot IO
+data Bot = Bot Handle
+
+socket :: Bot -> Handle
+socket (Bot h) = h
+
+-- Connect to the server and return the initial bot state
+connect :: String -> Int -> IO Bot
+connect s p = notify $ do
+ h <- connectTo s $ PortNumber (fromIntegral p)
+ hSetBuffering h NoBuffering
+ return $ Bot h
+ where notify a = bracket_
+ (print ("Connecting to " ++ s ++ "...") >> hFlush stdout)
+ (print "It is so.") a
+
+-- write $ Message (Maybe ett prefix) Command [parametrar]
+-- write $ Message Nothing "LOL" ["nyeyhehe"]
+write :: Message -> Net ()
+write msg = do
+ h <- asks socket
+ liftIO $ hPrint h msg
+ liftIO $ S.store msg
+ liftIO . putStrLn $ "sent: " ++ (show msg)
+
+-- Prettier ones for later use :)
+-- write msg = liftIO . flip hPrint msg =<< asks socket
+-- write = (asks socket >>=) . (liftIO .) . flip hPrint
+
+-- Process lines from the server
+listen :: Handle -> Net ()
+listen h = forever $ do
+ s <- init `fmap` liftIO (hGetLine h)
+ let Just msg = parseMessage s -- Uh oh!
+ liftIO . putStrLn $ "got: " ++ s
+ liftIO $ S.store msg -- Store every message in MongoDB
+ ping msg
+ where ping (Message _ "PING" p) = write $ Message Nothing "PONG" p
+ ping message = eval message
+
+-- Perform a command
+eval :: Message -> Net ()
+eval msg@(Message _ _ params@(p:_))
+ | ".join " `isPrefixOf` lastPar = write $ Message Nothing "JOIN"
+ (take 1 . drop 1 $ words lastPar)
+ | ".part " `isPrefixOf` lastPar = write $ Message Nothing "PART"
+ (take 1 . drop 1 $ words lastPar)
+ | "> " `isPrefixOf` lastPar = eval' E.evalHsExt msg
+ | ".type " `isPrefixOf` lastPar = eval' E.typeOf msg
+ | ".gtfo " `isPrefixOf` lastPar = write $ Message Nothing "QUIT" ["lol haahaha!"]
+ | ".seen " `isPrefixOf` lastPar = eval' S.seen msg
+ | ".pf " `isPrefixOf` lastPar = eval' E.pointFree msg
+ | otherwise = return ()
+ where eval' f msg = liftIO (f msg) >>= privmsg chan
+ lastPar = last params
+ chan = p
+
+eval (Message _ _ _) = return ()
+
+privmsg :: String -> String -> Net ()
+privmsg c m = write $ Message Nothing "PRIVMSG" [c, U.excerpt' m]
77 L.hs
@@ -0,0 +1,77 @@
+{-# LANGUAGE NoMonomorphismRestriction #-}
+module L where
+
+import Control.Arrow
+import Control.Monad
+import Control.Monad.State
+import Control.Monad.Trans
+import Data.Char
+import Data.Function
+import Data.Functor
+import Data.List
+import Data.Maybe
+import Data.Monoid
+import Data.Ratio
+import Numeric
+import System
+import System.IO
+import System.Process
+import System.Random
+
+interleave [] _ = []
+interleave (x:xs) ys = x : interleave ys xs
+
+padl n p s = pad n p s ++ s
+padr = (ap (++) .) . pad
+pad = (flip (drop . length) .) . replicate
+
+rgbToHex (r, g, b)
+ | all ok rgb = '#' : concatMap (padl 2 '0' . flip showHex "") rgb
+ | otherwise = error "All numbers must be >= 0 and <= 255"
+ where ok = liftM2 (&&) (<= 255) (>= 0)
+ rgb = [r, g, b]
+
+-- temp01's magical function
+oOo [] = [];
+oOo s = concat [init s, [toUpper (last s)], tail (reverse s)]
+
+type Peen = String
+ben :: Int -> Peen
+ben = ('8' :) . (++ "D") . flip replicate '='
+
+gf n | n < 0 = "NEGATIVE U"
+ | n == 0 = "N'T U"
+ | n <= 9000 = (unwords . replicate n) "NO" ++ " U"
+ | otherwise = "It's over 9000!"
+
+ajpiano = "PANDEMONIUM!!!"
+
+akahn s | last s == '?' = "did you mean " ++ s
+ | otherwise = "that's not " ++ s
+
+coldhead s | null s = ">: |"
+ | otherwise = "these are truly the last " ++ s
+
+dabear = ("your mom " ++)
+
+dytrivedi s | null s = "my wife is happy"
+ | otherwise = mappend "my wife is annoyed i spend so much time " s
+
+matjas = interleave "matjas" . enumFrom
+
+miketaylr s | null ts = "here, let me open that... wait a minute, there's nothing there, you bitch!"
+ | otherwise = unwords ["here, let me open that", s, "for you!"]
+ where ts = trim s
+
+nlogax = (++ "n't")
+paul_irish = "ryan_irelan"
+sean = "koole"
+seutje = ("I would of " ++)
+temp01 = Just "awesome"
+vladikoff = ("flod " ++) . (++ "!!")
+
+mlu = "much like urself"
+muu = "much unlike urself"
+
+trim = trim' . trim'
+ where trim' = reverse . dropWhile isSpace
30 LICENSE
@@ -0,0 +1,30 @@
+Copyright (c)2010, Jonas Westerlund
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ * Neither the name of Jonas Westerlund nor the names of other
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 Main.hs
@@ -0,0 +1,41 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+import Control.OldException
+import Control.Monad.Reader hiding (join)
+import Data.List
+import Prelude hiding (catch)
+import System.IO
+
+import IRC hiding (message)
+import Types
+
+--server = "wineasy.se.quakenet.org"
+server = "chat.us.freenode.net"
+port = 6667
+nickn = "ultror"
+name = "ultror"
+
+channels = intercalate "," ["#clojure",
+ "#jquery",
+ "#jquery-ot",
+ "#runlevel6",
+ "#ultror"]
+
+-- Set up actions to run on start and end, and run the main loop
+main :: IO ()
+main = bracket (connect server port) disconnect loop
+ where disconnect = hClose . socket
+ loop st = catch (runReaderT run st) (const $ return ())
+
+-- Join some channels, and start processing commands
+run :: Net ()
+run = do
+ write $ Message Nothing "USER" [nickn, "0", "*", name]
+ write $ Message Nothing "NICK" [nickn]
+ write $ Message Nothing "JOIN" [channels]
+ asks socket >>= listen
+
+-- Authorized bot wranglers
+admins = nicks ["ajpiano akahn BBonifield coldhead gf3 matjas miketaylr",
+ "nimbupani nlogax paul_irish seutje temp01"]
+ where nicks = words . unlines
63 Parser.hs
@@ -0,0 +1,63 @@
+module Parser where
+
+import Data.Maybe
+import Text.ParserCombinators.Parsec hiding (letter, space)
+import Types
+
+parseMessage :: String -> Maybe Message
+parseMessage s = either (const Nothing) Just (parse message "" s)
+
+-- <message> ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>
+message = do
+ p <- optionMaybe (char ':' >> prefix >>= (space >>) . return)
+ c <- command
+ ps <- many (space >> params)
+-- _ <- crlf
+ return $ Message p c ps
+-- <prefix> ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
+prefix = try nickPrefix <|> serverPrefix
+
+-- <command> ::= <letter> { <letter> } | <number> <number> <number>
+command = many1 letter <|> count 3 number -- Either one or more letters, or three numbers
+-- <SPACE> ::= ' ' { ' ' }
+space = many1 $ char '\SP'
+-- <params> ::= <SPACE> [ ':' <trailing> | <middle> <params> ]
+params = optionMaybe ((char ':' >> trailing) <|> middle) >>= return . fromMaybe ""
+-- <middle> ::= <Any *non-empty* sequence of octets not including SPACE or NUL or CR or LF, the first of which may not be ':'>
+middle = many1 nonWhite
+
+-- <trailing> ::= <Any, possibly *empty*, sequence of octets not including NUL or CR or LF>
+trailing = many $ noneOf "\NUL\CR\LF"
+-- <crlf> ::= CR LF
+crlf = string "\CR\LF"
+
+-- <host> ::= see RFC 952 [DNS:4] for details on allowed hostnames
+host = many1 $ noneOf " @!"
+
+-- <nick> ::= <letter> { <letter> | <number> | <special> }
+-- This is more liberal than the RFC, to make it work IRL
+nick = many1 (letter <|> number <|> special)
+
+nickPrefix = do
+ n <- nick
+ _ <- notFollowedBy $ char '.'
+ u <- optionMaybe (char '!' >> user)
+ h <- optionMaybe (char '@' >> host)
+ return $ NickName n u h
+
+
+serverName = host
+serverPrefix = serverName >>= return . Server
+
+-- <user> ::= <nonwhite> { <nonwhite> }
+user = many1 $ noneOf "\SP\NUL\CR\LF\64"
+
+-- <letter> ::= 'a' ... 'z' | 'A' ... 'Z'
+letter = oneOf $ ['a'..'z'] ++ ['A'..'Z']
+-- <number> ::= '0' ... '9'
+number = oneOf ['0'..'9']
+-- <special> ::= '-' | '[' | ']' | '\' | '`' | '^' | '{' | '}'
+special = oneOf "-[]\\`^{}_|" -- Added "_|", because IRL /= RFC
+
+-- <nonwhite> ::= <any 8bit code except SPACE (0x20), NUL (0x0), CR (0xd), and LF (0xa)>
+nonWhite = noneOf "\SP\NUL\CR\LF"
139 Seen.hs
@@ -0,0 +1,139 @@
+{-# Language OverloadedStrings #-}
+
+module Seen where
+
+import Control.Monad
+import Control.Monad.Trans (liftIO)
+import Control.Monad.State
+import qualified Data.Bson as B
+import Data.List hiding (sort, insert)
+import Data.Maybe
+import Data.Monoid
+import Data.Time
+import Data.UString (u)
+import Database.MongoDB
+import System.Random
+import Types
+
+import qualified Utils as U
+
+hostIP = "127.0.0.1"
+dbName = "seen"
+
+run = (runNet .) . flip (runConn . useDb dbName)
+connectDb = runNet . connect $ host hostIP
+
+store :: Message -> IO String
+store (Message (Just (NickName nick _ _)) command params) = do
+ conn <- connectDb
+ now <- getCurrentTime
+ let mess = last params
+ let chan = head params
+ let message = ["nick" =: nick,
+ "text" =: mess,
+ "what" =: command,
+ "chan" =: chan,
+ "date" =: now]
+ either (const $ return "MongoDB is down!")
+ (\con -> run con (insert "messages" message) >> return "Stored.")
+ conn
+
+store (Message _ _ _) = return "LOL!!"
+
+-- What to do about this mess?
+seen :: Message -> IO String
+seen (Message (Just (NickName n _ _)) _ params) = do
+ -- ".seen ultror " -> "ultror"
+ let nick = U.trim . unwords . take 1 . drop 1 . words . last $ params
+ conn <- connectDb
+ case conn of
+ Left _ -> return "MongoDB is down!"
+ Right con -> do
+ Right res <- run con
+ (findOne (select ["nick" =: Regex (mconcat [u"^", u (escape' nick), "$"]) "i"] "messages")
+ { sort = ["date" =: (-1 :: Int)] })
+ either (const $ return "Everyone died!")
+ (internet n nick) res
+
+seen (Message _ _ _) = return "nlogax fails at pattern matching."
+
+escape :: Char -> String
+escape c | c `elem` regexChars = '\\' : [c]
+ | otherwise = [c]
+ where regexChars = "\\+()^$.{}]|"
+escape' [] = []
+escape' (c:cs) = escape c ++ escape' cs
+
+internet n nick val = do
+ now <- getCurrentTime
+ let txt = ((B.lookup "text" $ fromMaybe [] val) :: Maybe String)
+ let cmd = ((B.lookup "what" $ fromMaybe [] val) :: Maybe String)
+ let chn = ((B.lookup "chan" $ fromMaybe [] val) :: Maybe String)
+ let whn = ((B.lookup "date" $ fromMaybe [] val) :: Maybe UTCTime)
+ maybe (return $ n ++ unwords [":", nick, "means nothing to me."])
+ (\msg -> return $ n ++ concat [": ", formatSeen nick msg (maybe "" id cmd) chn,
+ maybe "" ((' ' :) . timeAgo now) whn]) txt
+
+formatSeen nick msg "PRIVMSG" (Just chan)
+ | "\SOHACTION" `isPrefixOf` msg = concat [nick, " was all like *", nick, " ",
+ U.excerpt 60 "..." . init . drop 8 $ U.trim msg, "* in ", chan]
+ | otherwise = concat [nick, " said \"",
+ U.excerpt 60 "..." $ U.trim msg,
+ "\" in ", chan]
+
+formatSeen nick msg "PART" (Just chan) = unwords [nick, "left", chan, "with the message:", msg]
+formatSeen nick _ "JOIN" (Just chan) = unwords [nick, "joined", chan]
+formatSeen nick msg "QUIT" _ = unwords [nick, "quit with the message:", msg]
+formatSeen nick msg "NICK" _ = unwords [nick, "changed nick to", msg]
+formatSeen _ _ _ _ = "did something unspeakable"
+
+secret chan | chan `elem` secretChans = "#SECRET"
+ | otherwise = chan
+ where secretChans = ["#jquery-ot"]
+
+timeAgo now before = concatTime . relTime . round $ diffUTCTime now before
+
+relTime t | t < s = ["now"]
+ | t == s = ["1 second"]
+ | t < m = [show t ++ " seconds"]
+ | t == m = ["1 minute"]
+ | t < m * 2 = ["1 minute"] ++ rest m
+ | t < h = [first m ++ " minutes"] ++ rest m
+ | t == h = ["1 hour"]
+ | t < h * 2 = ["1 hour"] ++ rest h
+ | t < d = [first h ++ " hours"] ++ rest h
+ | t == d = ["1 day"]
+ | t < d * 2 = ["1 day"] ++ rest d
+ | t < w = [first d ++ " days"] ++ rest d
+ | t == w = ["1 week"]
+ | t < w * 2 = ["1 week"] ++ rest w
+ | t < w * 4 = [first w ++ " weeks"] ++ rest w
+ | otherwise = ["a long time"]
+ where first = show . div t
+ rest v | t `mod` v == 0 = []
+ | otherwise = relTime (mod t v)
+ s = 1
+ m = s * 60
+ h = m * 60
+ d = h * 24
+ w = d * 7
+
+concatTime xss@(x:xs) | x == "now" = x
+ | length xss == 1 = concat xss ++ " ago"
+ | otherwise = intercalate ", " (init xss) ++ " and " ++ last xss ++ " ago"
+concatTime [] = []
+
+rollDie :: State StdGen Int
+rollDie = do generator <- get
+ let (value, newGenerator) = randomR (1,6) generator
+ put newGenerator
+ return value
+
+{-
+punch :: Message -> String
+punch (Message (Just (NickName nick _ _)) _ params@(p:ps)) = "\SOHACTION punches " ++ hahaha ++ " in the face!\SOH"
+ where chan = head ps
+ text = last ps
+ target = takeWhile (/= ' ') . dropWhile (== ' ') . dropWhile (/= ' ') $ text
+ hahaha = if target == "ultror" then nick else target
+-}
2 Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
16 TODO
@@ -0,0 +1,16 @@
+Add support for multiple channels :)
+ The bot now replies to the channel the message originated from.
+ Not sure if anything else is needed, nice and stateless as it is.
+
+Add support for multiple networks :)
+ Or just run one bot per network, hehehe.
+
+Add support for CTCP messages
+ The Seen stuff now prints ACTIONs nicely.
+ Anything else will probably have to be handled on a per-message basis,
+ as CTCP messages are just like any other but with some \SOH thrown in.
+
+Add support for query
+ got: :nlogax!~nlogax@unaffiliated/nlogax PRIVMSG ultror :> 1
+ "sent: PRIVMSG ultror : 1\n"
+ got: :ultror!~ultror@c-a599e253.014-262-73746f13.cust.bredbandsbolaget.se PRIVMSG ultror : 1
40 Types.hs
@@ -0,0 +1,40 @@
+module Types where
+
+import Data.List
+
+data Message = Message (Maybe Prefix) Command Params
+ deriving (Eq, Read)
+
+-- The (optional) prefix can be either a servername or a nickname
+-- with optional username and host
+data Prefix = Server ServerName
+ | NickName String (Maybe UserName) (Maybe ServerName)
+ deriving (Eq, Read)
+
+type Command = String
+type Param = String
+type Params = [Param]
+type RealName = String
+type ServerName = String
+type UserName = String
+
+-- HMMM: Is this bad use of the Show class?
+instance Show Message where
+ show (Message (Just prefix) command params) =
+ intercalate " " [show prefix, command,
+ paramize params]
+ show (Message Nothing command params) =
+ intercalate " " [command, paramize params]
+
+instance Show Prefix where
+ show (Server s) = ':' : s
+ show (NickName nick user server) = ':' : nick
+ ++ maybe "" ('!' :) user
+ ++ maybe "" ('@' :) server
+
+-- TODO: Can colon-prefixed params only come last?
+-- What if other params contain spaces? Asplode?
+paramize :: Params -> String
+paramize [] = ""
+paramize [x] = ':' : x
+paramize ps = intercalate " " (init ps) ++ " :" ++ last ps
18 Utils.hs
@@ -0,0 +1,18 @@
+-- | Utilities for great justice
+module Utils where
+
+import Data.Char
+import Data.List
+
+-- * String utilities
+trim :: String -> String
+-- ^ Remove leading and trailing whitespace
+trim = trim' . trim'
+ where trim' = reverse . dropWhile isSpace
+
+excerpt :: Int -> String -> String -> String
+-- ^ Truncate string to given length, and end with a string
+excerpt len end s | null $ drop len s = s -- Don't check the length of `s`!
+ | otherwise = take (len - length end) s ++ end
+
+excerpt' = excerpt 200 "..."
58 norby.cabal
@@ -0,0 +1,58 @@
+-- norby.cabal auto-generated by cabal init. For additional options,
+-- see
+-- http://www.haskell.org/cabal/release/cabal-latest/doc/users-guide/authors.html#pkg-descr.
+-- The name of the package.
+Name: norby
+
+-- The package version. See the Haskell package versioning policy
+-- (http://www.haskell.org/haskellwiki/Package_versioning_policy) for
+-- standards guiding when and how versions should be incremented.
+Version: 0.1
+
+-- A short (one-line) description of the package.
+-- Synopsis:
+
+-- A longer description of the package.
+-- Description:
+
+-- The license under which the package is released.
+License: BSD3
+
+-- The file containing the license text.
+License-file: LICENSE
+
+-- The package author(s).
+Author: Jonas Westerlund
+
+-- An email address to which users can send suggestions, bug reports,
+-- and patches.
+Maintainer: jonas.westerlund@gmail.com
+
+-- A copyright notice.
+-- Copyright:
+
+Category: Network
+
+Build-type: Simple
+
+-- Extra files to be distributed with the package, such as examples or
+-- a README.
+-- Extra-source-files:
+
+-- Constraint on the version of Cabal needed to build this package.
+Cabal-version: >=1.2
+
+
+Executable norby
+ -- .hs or .lhs file containing the Main module.
+ Main-is: Main.hs
+
+ -- Packages needed in order to build this package.
+ Build-depends: Mueval >= 0.8 <= 1.0
+
+ -- Modules not exported by this package.
+ -- Other-modules:
+
+ -- Extra tools (e.g. alex, hsc2hs, ...) needed to build the source.
+ -- Build-tools:
+

0 comments on commit 3a0a32e

Please sign in to comment.
Something went wrong with that request. Please try again.