@@ -1,91 +1,156 @@
1{-# LANGUAGE DeriveDataTypeable #-}
1{-# LANGUAGE DeriveDataTypeable #-}
2module Main where
2module Main where
4+import Control.Monad (foldM, when)
5+import qualified Data.Map as Map
4import Data.Either (lefts, rights)
6import Data.Either (lefts, rights)
5−import Data.List (intersect, intercalate,lookup)
7+import Data.List (intersect, intercalate, lookup)
6import Data.Maybe (fromMaybe)
8import Data.Maybe (fromMaybe)
7import Data.Version (showVersion)
9import Data.Version (showVersion)
8import System.Console.CmdArgs
10import System.Console.CmdArgs
11+import System.Directory
9import System.Exit
12import System.Exit
10import System.FilePath
13import System.FilePath
14+import qualified Text.Blaze.Html.Renderer.Pretty as Pretty
11−import Text.Blaze.Html.Renderer.String (renderHtml)
15+import qualified Text.Blaze.Html.Renderer.String as Normal
13import qualified Text.Jasmine as JS
17import qualified Text.Jasmine as JS
14import qualified Data.ByteString.Lazy.Char8 as BS
18import qualified Data.ByteString.Lazy.Char8 as BS
17−import Initialize (build,buildFromSource)
21+import Initialize (buildFromSource, getSortedModuleNames)
18−import CompileToJS
22+import CompileToJS (jsModule)
19−import GenerateHtml
23+import GenerateHtml (createHtml, JSStyle(..), JSSource(..))
20import qualified Libraries as Libraries
24import qualified Libraries as Libraries
21import Paths_Elm
25import Paths_Elm
23−data ELM =
27+data Flags =
24− ELM { make :: Bool
28+ Flags { make :: Bool
25− , files :: [FilePath]
29+ , files :: [FilePath]
26− , runtime :: Maybe FilePath
30+ , runtime :: Maybe FilePath
27− , separate_js :: Bool
28− , only_js :: Bool
31+ , only_js :: Bool
29− , import_js :: [FilePath]
32+ , scripts :: [FilePath]
30− , no_prelude :: Bool
33+ , no_prelude :: Bool
32− , minify :: Bool
34+ , minify :: Bool
33− , output_directory :: Maybe FilePath
35+ , output_directory :: FilePath
35 deriving (Data,Typeable,Show,Eq)
37 deriving (Data,Typeable,Show,Eq)
37−elm = ELM { make = False &= help "automatically compile dependencies."
41+ &= help "automatically compile dependencies."
38− , files = def &= args &= typ "FILES"
42+ , files = def &= args &= typ "FILES"
39− , runtime = Nothing &= typFile &=
43+ , runtime = Nothing &= typFile
40− help "Specify a custom location for Elm's runtime system."
44+ &= help "Specify a custom location for Elm's runtime system."
41− , separate_js = False &= help "Compile to separate HTML and JS files."
45+ , only_js = False
42− , only_js = False &= help "Compile only to JavaScript."
46+ &= help "Compile only to JavaScript."
47+ , scripts = [] &= typFile
43− , import_js = [] &= typFile &= help "Include a JavaScript file before the body of the Elm program. Can be used many times. Files will be included in the given order."
48+ &= help "Load JavaScript files in generated HTML. Files will be included in the given order."
44− , no_prelude = False &= help "Do not import Prelude by default, used only when compiling standard libraries."
50+ &= help "Do not import Prelude by default, used only when compiling standard libraries."
45− , noscript = True &= help "Add generated <noscript> tag to HTML output."
51+ , minify = False
46− , minify = False &= help "Minify generated JavaScript"
52+ &= help "Minify generated JavaScript"
53+ , output_directory = "ElmFiles" &= typFile
47− , output_directory = Nothing &= typFile &= help "Output files to directory specified. Defaults to the location of original file."
54+ &= help "Output files to directory specified. Defaults to ElmFiles/ directory."
49− help "Compile Elm programs to HTML, CSS, and JavaScript." &=
55+ } &= help "Compile Elm programs to HTML, CSS, and JavaScript."
50− summary ("The Elm Compiler " ++ showVersion version ++ ", (c) Evan Czaplicki")
56+ &= summary ("The Elm Compiler " ++ showVersion version ++ ", (c) Evan Czaplicki")
52−main = do
58+main :: IO ()
53− args <- cmdArgs elm
59+main = compileArgs =<< cmdArgs flags
54− mini <- getDataFileName "elm-runtime.js"
60+
61+compileArgs :: Flags -> IO ()
55− compileArgs mini args
62+compileArgs flags =
56−
63+ let builder file = if make flags then build flags file
57−compileArgs mini flags =
64+ else buildFile flags 1 1 file >> return ()
58− case files flags of
65+ in case files flags of
59− [] -> putStrLn "Usage: elm [OPTIONS] [FILES]\nFor more help: elm --help"
66+ [] -> putStrLn "Usage: elm [OPTIONS] [FILES]\nFor more help: elm --help"
60− fs -> mapM_ (fileTo flags what loc) fs
67+ fs -> mapM_ builder fs
61− where loc = fromMaybe mini (runtime flags)
68+
62− what | only_js flags = JS
69+
63− | separate_js flags = Split
70+type Interface = String
64− | otherwise = HTML
71+
65−
72+buildFile :: Flags -> Int -> Int -> FilePath -> IO Interface
66−data What = JS | HTML | Split
73+buildFile flags moduleNum numModules filePath =
67−
74+ do compiled <- alreadyCompiled
68−fileTo flags what rtLoc file = do
75+ if compiled then getInterface else compile
69− let jsStyle = if minify flags then Minified else Readable
76+
70− formatJS = if minify flags then BS.unpack . JS.minify . BS.pack else id
77+ where
71− prelude = not (no_prelude flags)
78+ file :: String -> FilePath
72− ems <- if make flags then build prelude file
79+ file ext = replaceExtension filePath ext
73− else do src <- readFile file
80+
74− return (fmap (:[]) (buildFromSource prelude src))
81+ interface :: FilePath
75− jss <- concat `fmap` mapM readFile (import_js flags)
82+ interface = file "elmi"
77− Left err -> do putStrLn $ "Error while compiling " ++ file ++ ":\n" ++ err
84+ alreadyCompiled :: IO Bool
86+ exists <- doesFileExist interface
87+ if not exists then return False
88+ else do tsrc <- getModificationTime filePath
89+ tint <- getModificationTime interface
93+ number = "[" ++ show moduleNum ++ " of " ++ show numModules ++ "]"
96+ name = intercalate "." (splitDirectories (dropExtensions filePath))
98+ compile :: IO Interface
100+ putStrLn (number ++ " Compiling " ++ name)
101+ source <- readFile filePath
102+ js <- if takeExtension filePath == ".js" then return source else
103+ case buildFromSource (no_prelude flags) source of
78− exitFailure
104+ Left err -> putStrLn err >> exitFailure
79− Right ms' ->
105+ Right modul -> return (jsModule modul)
80− let path = fromMaybe "" (output_directory flags) </> file
106+ writeFile interface js
81− js = replaceExtension path ".js"
107+ writeFile (file "elmo") js
108+ when (only_js flags) (writeFile (file "js") js)
111+ getInterface :: IO Interface
115+getRuntime :: Flags -> IO FilePath
117+ case runtime flags of
118+ Just fp -> return fp
119+ Nothing -> getDataFileName "elm-runtime.js"
121+build :: Flags -> FilePath -> IO ()
122+build flags rootFile = do
123+ files <- getSortedModuleNames rootFile
124+ buildFiles flags (length files) Map.empty files
125+ js <- foldM appendToOutput "" files
126+ case only_js flags of
128+ putStr "\nGenerating JavaScript... "
82− html = replaceExtension path ".html"
129+ writeFile (replaceExtension rootFile "js") (genJs js)
83− ms = if no_prelude flags then ms' else map Libraries.addPrelude ms'
130+ putStrLn "Done"
84− txt = jss ++ concatMap jsModule ms
131+ False -> do
85− in case what of
132+ putStr "\nGenerating HTML... "
86− JS -> writeFile js (formatJS txt)
133+ runtime <- getRuntime flags
87− HTML -> writeFile html . renderHtml $
134+ let html = genHtml $ createHtml runtime rootFile (sources js) ""
88− modulesToHtml jsStyle "" rtLoc jss (noscript flags) ms
135+ writeFile (replaceExtension rootFile "html") html
89− Split ->
136+ putStrLn "Done"
90− do writeFile html . renderHtml $ linkedHtml rtLoc js ms
137+
91− writeFile js (formatJS txt)
138+ where
139+ appendToOutput :: String -> FilePath -> IO String
140+ appendToOutput js filePath =
141+ do src <- readFile (replaceExtension filePath "elmo")
144+ genHtml = if minify flags then Normal.renderHtml else Pretty.renderHtml
145+ genJs = if minify flags then BS.unpack . JS.minify . BS.pack else id
146+ sources js = map Link (scripts flags) ++
147+ [ Source (if minify flags then Minified else Readable) js ]
150+buildFiles :: Flags -> Int -> Map.Map String Interface -> [FilePath] -> IO ()
151+buildFiles _ _ _ [] = return ()
152+buildFiles flags numModules interfaces (filePath:rest) = do
153+ interface <- buildFile flags (numModules - length rest) numModules filePath
154+ let moduleName = intercalate "." (splitDirectories (dropExtensions filePath))
155+ interfaces' = Map.insert moduleName interface interfaces
156+ buildFiles flags numModules interfaces' rest