| feature | 0.15.1 | 0.16 |
|---|---|---|
| field update | { record | x <- 42 } |
{ record | x = 42 } |
| field addition | { record | x = 42 } |
removed |
| field deletion | { record - x } |
removed |
| record constructors that add fields |
type alias Named r =
{ r | name : String }
-- generates a function like this:
-- Named : String -> r -> Named r
|
type alias Named r =
{ r | name : String }
Generates no function. Field addition is gone. A function
will still be generated for "closed" records though.
|
| field parameters |
type alias Foo =
{ prefix : String -> String }
foo : Foo
foo = { prefix x = "prefix" ++ x }
|
type alias Foo =
{ prefix : String -> String }
foo : Foo
foo = { prefix = \x-> "prefix" ++ x }
|
| multi-way if |
if | x < 0 -> "left"
| x > 0 -> "right"
| otherwise -> "neither"
|
if x < 0 then
"left"
else if x > 0 then
"right"
else
"neither"
|
| feature | 0.16 | 0.17 |
|---|---|---|
| module declaration | module Queue (..) where |
module Queue exposing (..) |
| Initialize | 0.16 | 0.17 |
|---|---|---|
| Embed | Elm.embed(Elm.Main, someNode); |
Elm.Main.embed(someNode); |
| Fullscreen | Elm.fullscreen(Elm.Main); |
Elm.Main.fullscreen(); |
| Worker | Elm.worker(Elm.Main); |
Elm.Main.worker(); |
|] <> code <> [r|
|]
================================================
FILE: terminal/src/Develop/Generate/Index.hs
================================================
{-# LANGUAGE OverloadedStrings #-}
module Develop.Generate.Index
( generate
)
where
import Control.Monad (filterM)
import qualified Data.ByteString.Builder as B
import qualified Data.List as List
import qualified Data.Map as Map
import qualified System.Directory as Dir
import System.FilePath ((>), splitDirectories, takeExtension)
import qualified BackgroundWriter as BW
import qualified Develop.Generate.Help as Help
import qualified Elm.Details as Details
import qualified Elm.Outline as Outline
import qualified Elm.Package as Pkg
import qualified Elm.Version as V
import qualified Json.Encode as E
import Json.Encode ((==>))
import qualified Reporting
import qualified Stuff
-- GENERATE
generate :: FilePath -> IO B.Builder
generate pwd =
do flags <- getFlags pwd
return $ Help.makePageHtml "Index" (Just (encode flags))
-- FLAGS
data Flags =
Flags
{ _root :: FilePath
, _pwd :: [String]
, _dirs :: [FilePath]
, _files :: [File]
, _readme :: Maybe String
, _outline :: Maybe Outline.Outline
, _exactDeps :: Map.Map Pkg.Name V.Version
}
data File =
File
{ _path :: FilePath
, _runnable :: Bool
}
-- GET FLAGS
getFlags :: FilePath -> IO Flags
getFlags pwd =
do contents <- Dir.getDirectoryContents pwd
root <- Dir.getCurrentDirectory
dirs <- getDirs pwd contents
files <- getFiles pwd contents
readme <- getReadme pwd
outline <- getOutline
exactDeps <- getExactDeps outline
return $
Flags
{ _root = root
, _pwd = dropWhile ("." ==) (splitDirectories pwd)
, _dirs = dirs
, _files = files
, _readme = readme
, _outline = outline
, _exactDeps = exactDeps
}
-- README
getReadme :: FilePath -> IO (Maybe String)
getReadme dir =
do let readmePath = dir > "README.md"
exists <- Dir.doesFileExist readmePath
if exists
then Just <$> readFile readmePath
else return Nothing
-- GET DIRECTORIES
getDirs :: FilePath -> [FilePath] -> IO [FilePath]
getDirs pwd contents =
filterM (Dir.doesDirectoryExist . (pwd >)) contents
-- GET FILES
getFiles :: FilePath -> [FilePath] -> IO [File]
getFiles pwd contents =
do paths <- filterM (Dir.doesFileExist . (pwd >)) contents
mapM (toFile pwd) paths
toFile :: FilePath -> FilePath -> IO File
toFile pwd path =
if takeExtension path == ".elm" then
do source <- readFile (pwd > path)
let hasMain = List.isInfixOf "\nmain " source
return (File path hasMain)
else
return (File path False)
-- GET OUTLINE
getOutline :: IO (Maybe Outline.Outline)
getOutline =
do maybeRoot <- Stuff.findRoot
case maybeRoot of
Nothing ->
return Nothing
Just root ->
do result <- Outline.read root
case result of
Left _ -> return Nothing
Right outline -> return (Just outline)
-- GET EXACT DEPS
-- TODO revamp how `elm reactor` works so that this can go away.
-- I am trying to "just get it working again" at this point though.
--
getExactDeps :: Maybe Outline.Outline -> IO (Map.Map Pkg.Name V.Version)
getExactDeps maybeOutline =
case maybeOutline of
Nothing ->
return Map.empty
Just outline ->
case outline of
Outline.App _ ->
return Map.empty
Outline.Pkg _ ->
do maybeRoot <- Stuff.findRoot
case maybeRoot of
Nothing ->
return Map.empty
Just root ->
BW.withScope $ \scope ->
do result <- Details.load Reporting.silent scope root
case result of
Left _ ->
return Map.empty
Right (Details.Details _ validOutline _ _ _ _) ->
case validOutline of
Details.ValidApp _ ->
return Map.empty
Details.ValidPkg _ _ solution ->
return solution
-- ENCODE
encode :: Flags -> E.Value
encode (Flags root pwd dirs files readme outline exactDeps) =
E.object
[ "root" ==> encodeFilePath root
, "pwd" ==> E.list encodeFilePath pwd
, "dirs" ==> E.list encodeFilePath dirs
, "files" ==> E.list encodeFile files
, "readme" ==> maybe E.null E.chars readme
, "outline" ==> maybe E.null Outline.encode outline
, "exactDeps" ==> E.dict Pkg.toJsonString V.encode exactDeps
]
encodeFilePath :: FilePath -> E.Value
encodeFilePath filePath =
E.chars filePath
encodeFile :: File -> E.Value
encodeFile (File path hasMain) =
E.object
[ "name" ==> encodeFilePath path
, "runnable" ==> E.bool hasMain
]
================================================
FILE: terminal/src/Develop/Socket.hs
================================================
{-# LANGUAGE OverloadedStrings #-}
module Develop.Socket (watchFile) where
import Control.Concurrent (forkIO, threadDelay)
import Control.Exception (SomeException, catch)
import qualified Data.ByteString.Char8 as BS
import qualified Network.WebSockets as WS
import qualified System.FSNotify.Devel as Notify
import qualified System.FSNotify as Notify
watchFile :: FilePath -> WS.PendingConnection -> IO ()
watchFile watchedFile pendingConnection =
do connection <- WS.acceptRequest pendingConnection
Notify.withManager $ \mgmt ->
do stop <- Notify.treeExtAny mgmt "." ".elm" print
tend connection
stop
tend :: WS.Connection -> IO ()
tend connection =
let
pinger :: Integer -> IO a
pinger n =
do threadDelay (5 * 1000 * 1000)
WS.sendPing connection (BS.pack (show n))
pinger (n + 1)
receiver :: IO ()
receiver =
do _ <- WS.receiveDataMessage connection
receiver
shutdown :: SomeException -> IO ()
shutdown _ =
return ()
in
do _pid <- forkIO (receiver `catch` shutdown)
pinger 1 `catch` shutdown
================================================
FILE: terminal/src/Develop/StaticFiles/Build.hs
================================================
{-# LANGUAGE OverloadedStrings #-}
module Develop.StaticFiles.Build
( readAsset
, buildReactorFrontEnd
)
where
import qualified Data.ByteString as BS
import qualified Data.ByteString.Builder as B
import qualified Data.ByteString.Lazy as LBS
import qualified Data.NonEmptyList as NE
import qualified System.Directory as Dir
import System.FilePath ((>))
import qualified BackgroundWriter as BW
import qualified Build
import qualified Elm.Details as Details
import qualified Generate
import qualified Reporting
import qualified Reporting.Exit as Exit
import qualified Reporting.Task as Task
-- ASSETS
readAsset :: FilePath -> IO BS.ByteString
readAsset path =
BS.readFile ("reactor" > "assets" > path)
-- BUILD REACTOR ELM
buildReactorFrontEnd :: IO BS.ByteString
buildReactorFrontEnd =
BW.withScope $ \scope ->
Dir.withCurrentDirectory "reactor" $
do root <- Dir.getCurrentDirectory
runTaskUnsafe $
do details <- Task.eio Exit.ReactorBadDetails $ Details.load Reporting.silent scope root
artifacts <- Task.eio Exit.ReactorBadBuild $ Build.fromPaths Reporting.silent root details paths
javascript <- Task.mapError Exit.ReactorBadGenerate $ Generate.prod root details artifacts
return (LBS.toStrict (B.toLazyByteString javascript))
paths :: NE.List FilePath
paths =
NE.List
("src" > "NotFound.elm")
[ "src" > "Errors.elm"
, "src" > "Index.elm"
]
runTaskUnsafe :: Task.Task Exit.Reactor a -> IO a
runTaskUnsafe task =
do result <- Task.run task
case result of
Right a ->
return a
Left exit ->
do Exit.toStderr (Exit.reactorToReport exit)
error
"\n--------------------------------------------------------\
\\nError in Develop.StaticFiles.Build.buildReactorFrontEnd\
\\nCompile with `elm make` directly to figure it out faster\
\\n--------------------------------------------------------\
\\n"
================================================
FILE: terminal/src/Develop/StaticFiles.hs
================================================
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module Develop.StaticFiles
( lookup
, cssPath
, elmPath
, waitingPath
)
where
import Prelude hiding (lookup)
import qualified Data.ByteString as BS
import Data.FileEmbed (bsToExp)
import qualified Data.HashMap.Strict as HM
import Language.Haskell.TH (runIO)
import System.FilePath ((>))
import qualified Develop.StaticFiles.Build as Build
-- FILE LOOKUP
type MimeType =
BS.ByteString
lookup :: FilePath -> Maybe (BS.ByteString, MimeType)
lookup path =
HM.lookup path dict
dict :: HM.HashMap FilePath (BS.ByteString, MimeType)
dict =
HM.fromList
[ faviconPath ==> (favicon , "image/x-icon")
, elmPath ==> (elm , "application/javascript")
, cssPath ==> (css , "text/css")
, codeFontPath ==> (codeFont, "font/ttf")
, sansFontPath ==> (sansFont, "font/ttf")
]
(==>) :: a -> b -> (a,b)
(==>) a b =
(a, b)
-- PATHS
faviconPath :: FilePath
faviconPath =
"favicon.ico"
waitingPath :: FilePath
waitingPath =
"_elm" > "waiting.gif"
elmPath :: FilePath
elmPath =
"_elm" > "elm.js"
cssPath :: FilePath
cssPath =
"_elm" > "styles.css"
codeFontPath :: FilePath
codeFontPath =
"_elm" > "source-code-pro.ttf"
sansFontPath :: FilePath
sansFontPath =
"_elm" > "source-sans-pro.ttf"
-- ELM
elm :: BS.ByteString
elm =
$(bsToExp =<< runIO Build.buildReactorFrontEnd)
-- CSS
css :: BS.ByteString
css =
$(bsToExp =<< runIO (Build.readAsset "styles.css"))
-- FONTS
codeFont :: BS.ByteString
codeFont =
$(bsToExp =<< runIO (Build.readAsset "source-code-pro.ttf"))
sansFont :: BS.ByteString
sansFont =
$(bsToExp =<< runIO (Build.readAsset "source-sans-pro.ttf"))
-- IMAGES
favicon :: BS.ByteString
favicon =
$(bsToExp =<< runIO (Build.readAsset "favicon.ico"))
================================================
FILE: terminal/src/Develop.hs
================================================
{-# LANGUAGE OverloadedStrings #-}
module Develop
( Flags(..)
, run
)
where
import Control.Applicative ((<|>))
import Control.Monad (guard)
import Control.Monad.Trans (MonadIO(liftIO))
import qualified Data.ByteString.Builder as B
import qualified Data.ByteString as BS
import qualified Data.HashMap.Strict as HashMap
import qualified Data.NonEmptyList as NE
import qualified System.Directory as Dir
import System.FilePath as FP
import Snap.Core hiding (path)
import Snap.Http.Server
import Snap.Util.FileServe
import qualified BackgroundWriter as BW
import qualified Build
import qualified Elm.Details as Details
import qualified Develop.Generate.Help as Help
import qualified Develop.Generate.Index as Index
import qualified Develop.StaticFiles as StaticFiles
import qualified Generate.Html as Html
import qualified Generate
import qualified Reporting
import qualified Reporting.Exit as Exit
import qualified Reporting.Task as Task
import qualified Stuff
-- RUN THE DEV SERVER
data Flags =
Flags
{ _port :: Maybe Int
}
run :: () -> Flags -> IO ()
run () (Flags maybePort) =
do let port = maybe 8000 id maybePort
putStrLn $ "Go to http://localhost:" ++ show port ++ " to see your project dashboard."
httpServe (config port) $
serveFiles
<|> serveDirectoryWith directoryConfig "."
<|> serveAssets
<|> error404
config :: Int -> Config Snap a
config port =
setVerbose False $ setPort port $
setAccessLog ConfigNoLog $ setErrorLog ConfigNoLog $ defaultConfig
-- INDEX
directoryConfig :: MonadSnap m => DirectoryConfig m
directoryConfig =
fancyDirectoryConfig
{ indexFiles = []
, indexGenerator = \pwd ->
do modifyResponse $ setContentType "text/html;charset=utf-8"
writeBuilder =<< liftIO (Index.generate pwd)
}
-- NOT FOUND
error404 :: Snap ()
error404 =
do modifyResponse $ setResponseStatus 404 "Not Found"
modifyResponse $ setContentType "text/html;charset=utf-8"
writeBuilder $ Help.makePageHtml "NotFound" Nothing
-- SERVE FILES
serveFiles :: Snap ()
serveFiles =
do path <- getSafePath
guard =<< liftIO (Dir.doesFileExist path)
serveElm path <|> serveFilePretty path
-- SERVE FILES + CODE HIGHLIGHTING
serveFilePretty :: FilePath -> Snap ()
serveFilePretty path =
let
possibleExtensions =
getSubExts (takeExtensions path)
in
case mconcat (map lookupMimeType possibleExtensions) of
Nothing ->
serveCode path
Just mimeType ->
serveFileAs mimeType path
getSubExts :: String -> [String]
getSubExts fullExtension =
if null fullExtension then
[]
else
fullExtension : getSubExts (takeExtensions (drop 1 fullExtension))
serveCode :: String -> Snap ()
serveCode path =
do code <- liftIO (BS.readFile path)
modifyResponse (setContentType "text/html")
writeBuilder $
Help.makeCodeHtml ('~' : '/' : path) (B.byteString code)
-- SERVE ELM
serveElm :: FilePath -> Snap ()
serveElm path =
do guard (takeExtension path == ".elm")
modifyResponse (setContentType "text/html")
result <- liftIO $ compile path
case result of
Right builder ->
writeBuilder builder
Left exit ->
writeBuilder $ Help.makePageHtml "Errors" $ Just $
Exit.toJson $ Exit.reactorToReport exit
compile :: FilePath -> IO (Either Exit.Reactor B.Builder)
compile path =
do maybeRoot <- Stuff.findRoot
case maybeRoot of
Nothing ->
return $ Left $ Exit.ReactorNoOutline
Just root ->
BW.withScope $ \scope -> Stuff.withRootLock root $ Task.run $
do details <- Task.eio Exit.ReactorBadDetails $ Details.load Reporting.silent scope root
artifacts <- Task.eio Exit.ReactorBadBuild $ Build.fromPaths Reporting.silent root details (NE.List path [])
javascript <- Task.mapError Exit.ReactorBadGenerate $ Generate.dev root details artifacts
let (NE.List name _) = Build.getRootNames artifacts
return $ Html.sandwich name javascript
-- SERVE STATIC ASSETS
serveAssets :: Snap ()
serveAssets =
do path <- getSafePath
case StaticFiles.lookup path of
Nothing ->
pass
Just (content, mimeType) ->
do modifyResponse (setContentType (mimeType <> ";charset=utf-8"))
writeBS content
-- MIME TYPES
lookupMimeType :: FilePath -> Maybe BS.ByteString
lookupMimeType ext =
HashMap.lookup ext mimeTypeDict
(==>) :: a -> b -> (a,b)
(==>) a b =
(a, b)
mimeTypeDict :: HashMap.HashMap FilePath BS.ByteString
mimeTypeDict =
HashMap.fromList
[ ".asc" ==> "text/plain"
, ".asf" ==> "video/x-ms-asf"
, ".asx" ==> "video/x-ms-asf"
, ".avi" ==> "video/x-msvideo"
, ".bz2" ==> "application/x-bzip"
, ".css" ==> "text/css"
, ".dtd" ==> "text/xml"
, ".dvi" ==> "application/x-dvi"
, ".gif" ==> "image/gif"
, ".gz" ==> "application/x-gzip"
, ".htm" ==> "text/html"
, ".html" ==> "text/html"
, ".ico" ==> "image/x-icon"
, ".jpeg" ==> "image/jpeg"
, ".jpg" ==> "image/jpeg"
, ".js" ==> "text/javascript"
, ".json" ==> "application/json"
, ".m3u" ==> "audio/x-mpegurl"
, ".mov" ==> "video/quicktime"
, ".mp3" ==> "audio/mpeg"
, ".mp4" ==> "video/mp4"
, ".mpeg" ==> "video/mpeg"
, ".mpg" ==> "video/mpeg"
, ".ogg" ==> "application/ogg"
, ".otf" ==> "font/otf"
, ".pac" ==> "application/x-ns-proxy-autoconfig"
, ".pdf" ==> "application/pdf"
, ".png" ==> "image/png"
, ".qt" ==> "video/quicktime"
, ".sfnt" ==> "font/sfnt"
, ".sig" ==> "application/pgp-signature"
, ".spl" ==> "application/futuresplash"
, ".svg" ==> "image/svg+xml"
, ".swf" ==> "application/x-shockwave-flash"
, ".tar" ==> "application/x-tar"
, ".tar.bz2" ==> "application/x-bzip-compressed-tar"
, ".tar.gz" ==> "application/x-tgz"
, ".tbz" ==> "application/x-bzip-compressed-tar"
, ".text" ==> "text/plain"
, ".tgz" ==> "application/x-tgz"
, ".ttf" ==> "font/ttf"
, ".txt" ==> "text/plain"
, ".wav" ==> "audio/x-wav"
, ".wax" ==> "audio/x-ms-wax"
, ".webm" ==> "video/webm"
, ".webp" ==> "image/webp"
, ".wma" ==> "audio/x-ms-wma"
, ".wmv" ==> "video/x-ms-wmv"
, ".woff" ==> "font/woff"
, ".woff2" ==> "font/woff2"
, ".xbm" ==> "image/x-xbitmap"
, ".xml" ==> "text/xml"
, ".xpm" ==> "image/x-xpixmap"
, ".xwd" ==> "image/x-xwindowdump"
, ".zip" ==> "application/zip"
]
================================================
FILE: terminal/src/Diff.hs
================================================
{-# LANGUAGE OverloadedStrings #-}
module Diff
( Args(..)
, run
)
where
import qualified Data.List as List
import qualified Data.Map as Map
import qualified Data.Maybe as Maybe
import qualified Data.Name as Name
import qualified Data.NonEmptyList as NE
import qualified BackgroundWriter as BW
import qualified Build
import Deps.Diff (PackageChanges(..), ModuleChanges(..), Changes(..))
import qualified Deps.Diff as DD
import qualified Deps.Registry as Registry
import qualified Elm.Compiler.Type as Type
import qualified Elm.Details as Details
import qualified Elm.Docs as Docs
import qualified Elm.Magnitude as M
import qualified Elm.Outline as Outline
import qualified Elm.Package as Pkg
import qualified Elm.Version as V
import qualified Http
import qualified Reporting
import Reporting.Doc ((<+>))
import qualified Reporting.Doc as D
import qualified Reporting.Exit as Exit
import qualified Reporting.Exit.Help as Help
import qualified Reporting.Render.Type.Localizer as L
import qualified Reporting.Task as Task
import qualified Stuff
-- RUN
data Args
= CodeVsLatest
| CodeVsExactly V.Version
| LocalInquiry V.Version V.Version
| GlobalInquiry Pkg.Name V.Version V.Version
run :: Args -> () -> IO ()
run args () =
Reporting.attempt Exit.diffToReport $
Task.run $
do env <- getEnv
diff env args
-- ENVIRONMENT
data Env =
Env
{ _maybeRoot :: Maybe FilePath
, _cache :: Stuff.PackageCache
, _manager :: Http.Manager
, _registry :: Registry.Registry
}
getEnv :: Task Env
getEnv =
do maybeRoot <- Task.io $ Stuff.findRoot
cache <- Task.io $ Stuff.getPackageCache
manager <- Task.io $ Http.getManager
registry <- Task.eio Exit.DiffMustHaveLatestRegistry $ Registry.latest manager cache
return (Env maybeRoot cache manager registry)
-- DIFF
type Task a =
Task.Task Exit.Diff a
diff :: Env -> Args -> Task ()
diff env@(Env _ _ _ registry) args =
case args of
GlobalInquiry name v1 v2 ->
case Registry.getVersions' name registry of
Right vsns ->
do oldDocs <- getDocs env name vsns (min v1 v2)
newDocs <- getDocs env name vsns (max v1 v2)
writeDiff oldDocs newDocs
Left suggestions ->
Task.throw $ Exit.DiffUnknownPackage name suggestions
LocalInquiry v1 v2 ->
do (name, vsns) <- readOutline env
oldDocs <- getDocs env name vsns (min v1 v2)
newDocs <- getDocs env name vsns (max v1 v2)
writeDiff oldDocs newDocs
CodeVsLatest ->
do (name, vsns) <- readOutline env
oldDocs <- getLatestDocs env name vsns
newDocs <- generateDocs env
writeDiff oldDocs newDocs
CodeVsExactly version ->
do (name, vsns) <- readOutline env
oldDocs <- getDocs env name vsns version
newDocs <- generateDocs env
writeDiff oldDocs newDocs
-- GET DOCS
getDocs :: Env -> Pkg.Name -> Registry.KnownVersions -> V.Version -> Task Docs.Documentation
getDocs (Env _ cache manager _) name (Registry.KnownVersions latest previous) version =
if latest == version || elem version previous
then Task.eio (Exit.DiffDocsProblem version) $ DD.getDocs cache manager name version
else Task.throw $ Exit.DiffUnknownVersion name version (latest:previous)
getLatestDocs :: Env -> Pkg.Name -> Registry.KnownVersions -> Task Docs.Documentation
getLatestDocs (Env _ cache manager _) name (Registry.KnownVersions latest _) =
Task.eio (Exit.DiffDocsProblem latest) $ DD.getDocs cache manager name latest
-- READ OUTLINE
readOutline :: Env -> Task (Pkg.Name, Registry.KnownVersions)
readOutline (Env maybeRoot _ _ registry) =
case maybeRoot of
Nothing ->
Task.throw $ Exit.DiffNoOutline
Just root ->
do result <- Task.io $ Outline.read root
case result of
Left err ->
Task.throw $ Exit.DiffBadOutline err
Right outline ->
case outline of
Outline.App _ ->
Task.throw $ Exit.DiffApplication
Outline.Pkg (Outline.PkgOutline pkg _ _ _ _ _ _ _) ->
case Registry.getVersions pkg registry of
Just vsns -> return (pkg, vsns)
Nothing -> Task.throw Exit.DiffUnpublished
-- GENERATE DOCS
generateDocs :: Env -> Task Docs.Documentation
generateDocs (Env maybeRoot _ _ _) =
case maybeRoot of
Nothing ->
Task.throw $ Exit.DiffNoOutline
Just root ->
do details <-
Task.eio Exit.DiffBadDetails $ BW.withScope $ \scope ->
Details.load Reporting.silent scope root
case Details._outline details of
Details.ValidApp _ ->
Task.throw $ Exit.DiffApplication
Details.ValidPkg _ exposed _ ->
case exposed of
[] ->
Task.throw $ Exit.DiffNoExposed
e:es ->
Task.eio Exit.DiffBadBuild $
Build.fromExposed Reporting.silent root details Build.KeepDocs (NE.List e es)
-- WRITE DIFF
writeDiff :: Docs.Documentation -> Docs.Documentation -> Task ()
writeDiff oldDocs newDocs =
let
changes = DD.diff oldDocs newDocs
localizer = L.fromNames (Map.union oldDocs newDocs)
in
Task.io $ Help.toStdout $ toDoc localizer changes <> "\n"
-- TO DOC
toDoc :: L.Localizer -> PackageChanges -> D.Doc
toDoc localizer changes@(PackageChanges added changed removed) =
if null added && Map.null changed && null removed then
"No API changes detected, so this is a" <+> D.green "PATCH" <+> "change."
else
let
magDoc =
D.fromChars (M.toChars (DD.toMagnitude changes))
header =
"This is a" <+> D.green magDoc <+> "change."
addedChunk =
if null added then [] else
[ Chunk "ADDED MODULES" M.MINOR $
D.vcat $ map D.fromName added
]
removedChunk =
if null removed then [] else
[ Chunk "REMOVED MODULES" M.MAJOR $
D.vcat $ map D.fromName removed
]
chunks =
addedChunk ++ removedChunk ++ map (changesToChunk localizer) (Map.toList changed)
in
D.vcat (header : "" : map chunkToDoc chunks)
data Chunk =
Chunk
{ _title :: String
, _magnitude :: M.Magnitude
, _details :: D.Doc
}
chunkToDoc :: Chunk -> D.Doc
chunkToDoc (Chunk title magnitude details) =
let
header =
"----" <+> D.fromChars title <+> "-" <+> D.fromChars (M.toChars magnitude) <+> "----"
in
D.vcat
[ D.dullcyan header
, ""
, D.indent 4 details
, ""
, ""
]
changesToChunk :: L.Localizer -> (Name.Name, ModuleChanges) -> Chunk
changesToChunk localizer (name, changes@(ModuleChanges unions aliases values binops)) =
let
magnitude =
DD.moduleChangeMagnitude changes
(unionAdd, unionChange, unionRemove) =
changesToDocTriple (unionToDoc localizer) unions
(aliasAdd, aliasChange, aliasRemove) =
changesToDocTriple (aliasToDoc localizer) aliases
(valueAdd, valueChange, valueRemove) =
changesToDocTriple (valueToDoc localizer) values
(binopAdd, binopChange, binopRemove) =
changesToDocTriple (binopToDoc localizer) binops
in
Chunk (Name.toChars name) magnitude $
D.vcat $ List.intersperse "" $ Maybe.catMaybes $
[ changesToDoc "Added" unionAdd aliasAdd valueAdd binopAdd
, changesToDoc "Removed" unionRemove aliasRemove valueRemove binopRemove
, changesToDoc "Changed" unionChange aliasChange valueChange binopChange
]
changesToDocTriple :: (k -> v -> D.Doc) -> Changes k v -> ([D.Doc], [D.Doc], [D.Doc])
changesToDocTriple entryToDoc (Changes added changed removed) =
let
indented (name, value) =
D.indent 4 (entryToDoc name value)
diffed (name, (oldValue, newValue)) =
D.vcat
[ " - " <> entryToDoc name oldValue
, " + " <> entryToDoc name newValue
, ""
]
in
( map indented (Map.toList added)
, map diffed (Map.toList changed)
, map indented (Map.toList removed)
)
changesToDoc :: String -> [D.Doc] -> [D.Doc] -> [D.Doc] -> [D.Doc] -> Maybe D.Doc
changesToDoc categoryName unions aliases values binops =
if null unions && null aliases && null values && null binops then
Nothing
else
Just $ D.vcat $
D.fromChars categoryName <> ":" : unions ++ aliases ++ binops ++ values
unionToDoc :: L.Localizer -> Name.Name -> Docs.Union -> D.Doc
unionToDoc localizer name (Docs.Union _ tvars ctors) =
let
setup =
"type" <+> D.fromName name <+> D.hsep (map D.fromName tvars)
ctorDoc (ctor, tipes) =
typeDoc localizer (Type.Type ctor tipes)
in
D.hang 4 (D.sep (setup : zipWith (<+>) ("=" : repeat "|") (map ctorDoc ctors)))
aliasToDoc :: L.Localizer -> Name.Name -> Docs.Alias -> D.Doc
aliasToDoc localizer name (Docs.Alias _ tvars tipe) =
let
declaration =
"type" <+> "alias" <+> D.hsep (map D.fromName (name:tvars)) <+> "="
in
D.hang 4 (D.sep [ declaration, typeDoc localizer tipe ])
valueToDoc :: L.Localizer -> Name.Name -> Docs.Value -> D.Doc
valueToDoc localizer name (Docs.Value _ tipe) =
D.hang 4 $ D.sep [ D.fromName name <+> ":", typeDoc localizer tipe ]
binopToDoc :: L.Localizer -> Name.Name -> Docs.Binop -> D.Doc
binopToDoc localizer name (Docs.Binop _ tipe associativity (Docs.Precedence n)) =
"(" <> D.fromName name <> ")" <+> ":" <+> typeDoc localizer tipe <> D.black details
where
details =
" (" <> D.fromName assoc <> "/" <> D.fromInt n <> ")"
assoc =
case associativity of
Docs.Left -> "left"
Docs.Non -> "non"
Docs.Right -> "right"
typeDoc :: L.Localizer -> Type.Type -> D.Doc
typeDoc localizer tipe =
Type.toDoc localizer Type.None tipe
================================================
FILE: terminal/src/Init.hs
================================================
{-# LANGUAGE OverloadedStrings #-}
module Init
( run
)
where
import Prelude hiding (init)
import qualified Data.Map as Map
import qualified Data.NonEmptyList as NE
import qualified System.Directory as Dir
import qualified Deps.Solver as Solver
import qualified Elm.Constraint as Con
import qualified Elm.Outline as Outline
import qualified Elm.Package as Pkg
import qualified Elm.Version as V
import qualified Reporting
import qualified Reporting.Doc as D
import qualified Reporting.Exit as Exit
-- RUN
run :: () -> () -> IO ()
run () () =
Reporting.attempt Exit.initToReport $
do exists <- Dir.doesFileExist "elm.json"
if exists
then return (Left Exit.InitAlreadyExists)
else
do approved <- Reporting.ask question
if approved
then init
else
do putStrLn "Okay, I did not make any changes!"
return (Right ())
question :: D.Doc
question =
D.stack
[ D.fillSep
["Hello!"
,"Elm","projects","always","start","with","an",D.green "elm.json","file."
,"I","can","create","them!"
]
, D.reflow
"Now you may be wondering, what will be in this file? How do I add Elm files to\
\ my project? How do I see it in the browser? How will my code grow? Do I need\
\ more directories? What about tests? Etc."
, D.fillSep
["Check","out",D.cyan (D.fromChars (D.makeLink "init"))
,"for","all","the","answers!"
]
, "Knowing all that, would you like me to create an elm.json file now? [Y/n]: "
]
-- INIT
init :: IO (Either Exit.Init ())
init =
do eitherEnv <- Solver.initEnv
case eitherEnv of
Left problem ->
return (Left (Exit.InitRegistryProblem problem))
Right (Solver.Env cache _ connection registry) ->
do result <- Solver.verify cache connection registry defaults
case result of
Solver.Err exit ->
return (Left (Exit.InitSolverProblem exit))
Solver.NoSolution ->
return (Left (Exit.InitNoSolution (Map.keys defaults)))
Solver.NoOfflineSolution ->
return (Left (Exit.InitNoOfflineSolution (Map.keys defaults)))
Solver.Ok details ->
let
solution = Map.map (\(Solver.Details vsn _) -> vsn) details
directs = Map.intersection solution defaults
indirects = Map.difference solution defaults
in
do Dir.createDirectoryIfMissing True "src"
Outline.write "." $ Outline.App $
Outline.AppOutline V.compiler (NE.List (Outline.RelativeSrcDir "src") []) directs indirects Map.empty Map.empty
putStrLn "Okay, I created it. Now read that link!"
return (Right ())
defaults :: Map.Map Pkg.Name Con.Constraint
defaults =
Map.fromList
[ (Pkg.core, Con.anything)
, (Pkg.browser, Con.anything)
, (Pkg.html, Con.anything)
]
================================================
FILE: terminal/src/Install.hs
================================================
{-# LANGUAGE OverloadedStrings #-}
module Install
( Args(..)
, run
)
where
import Data.Map ((!))
import qualified Data.Map as Map
import qualified Data.Map.Merge.Strict as Map
import qualified BackgroundWriter as BW
import qualified Deps.Solver as Solver
import qualified Deps.Registry as Registry
import qualified Elm.Constraint as C
import qualified Elm.Details as Details
import qualified Elm.Package as Pkg
import qualified Elm.Outline as Outline
import qualified Elm.Version as V
import qualified Reporting
import Reporting.Doc ((<+>))
import qualified Reporting.Doc as D
import qualified Reporting.Exit as Exit
import qualified Reporting.Task as Task
import qualified Stuff
-- RUN
data Args
= NoArgs
| Install Pkg.Name
run :: Args -> () -> IO ()
run args () =
Reporting.attempt Exit.installToReport $
do maybeRoot <- Stuff.findRoot
case maybeRoot of
Nothing ->
return (Left Exit.InstallNoOutline)
Just root ->
case args of
NoArgs ->
do elmHome <- Stuff.getElmHome
return (Left (Exit.InstallNoArgs elmHome))
Install pkg ->
Task.run $
do env <- Task.eio Exit.InstallBadRegistry $ Solver.initEnv
oldOutline <- Task.eio Exit.InstallBadOutline $ Outline.read root
case oldOutline of
Outline.App outline ->
do changes <- makeAppPlan env pkg outline
attemptChanges root env oldOutline V.toChars changes
Outline.Pkg outline ->
do changes <- makePkgPlan env pkg outline
attemptChanges root env oldOutline C.toChars changes
-- ATTEMPT CHANGES
data Changes vsn
= AlreadyInstalled
| PromoteTest Outline.Outline
| PromoteIndirect Outline.Outline
| Changes (Map.Map Pkg.Name (Change vsn)) Outline.Outline
type Task = Task.Task Exit.Install
attemptChanges :: FilePath -> Solver.Env -> Outline.Outline -> (a -> String) -> Changes a -> Task ()
attemptChanges root env oldOutline toChars changes =
case changes of
AlreadyInstalled ->
Task.io $ putStrLn "It is already installed!"
PromoteIndirect newOutline ->
attemptChangesHelp root env oldOutline newOutline $
D.vcat
[ D.fillSep
["I","found","it","in","your","elm.json","file,"
,"but","in","the",D.dullyellow "\"indirect\"","dependencies."
]
, D.fillSep
["Should","I","move","it","into",D.green "\"direct\""
,"dependencies","for","more","general","use?","[Y/n]: "
]
]
PromoteTest newOutline ->
attemptChangesHelp root env oldOutline newOutline $
D.vcat
[ D.fillSep
["I","found","it","in","your","elm.json","file,"
,"but","in","the",D.dullyellow "\"test-dependencies\"","field."
]
, D.fillSep
["Should","I","move","it","into",D.green "\"dependencies\""
,"for","more","general","use?","[Y/n]: "
]
]
Changes changeDict newOutline ->
let
widths = Map.foldrWithKey (widen toChars) (Widths 0 0 0) changeDict
changeDocs = Map.foldrWithKey (addChange toChars widths) (Docs [] [] []) changeDict
in
attemptChangesHelp root env oldOutline newOutline $ D.vcat $
[ "Here is my plan:"
, viewChangeDocs changeDocs
, ""
, "Would you like me to update your elm.json accordingly? [Y/n]: "
]
attemptChangesHelp :: FilePath -> Solver.Env -> Outline.Outline -> Outline.Outline -> D.Doc -> Task ()
attemptChangesHelp root env oldOutline newOutline question =
Task.eio Exit.InstallBadDetails $
BW.withScope $ \scope ->
do approved <- Reporting.ask question
if approved
then
do Outline.write root newOutline
result <- Details.verifyInstall scope root env newOutline
case result of
Left exit ->
do Outline.write root oldOutline
return (Left exit)
Right () ->
do putStrLn "Success!"
return (Right ())
else
do putStrLn "Okay, I did not change anything!"
return (Right ())
-- MAKE APP PLAN
makeAppPlan :: Solver.Env -> Pkg.Name -> Outline.AppOutline -> Task (Changes V.Version)
makeAppPlan (Solver.Env cache _ connection registry) pkg outline@(Outline.AppOutline _ _ direct indirect testDirect testIndirect) =
if Map.member pkg direct then
return AlreadyInstalled
else
-- is it already indirect?
case Map.lookup pkg indirect of
Just vsn ->
return $ PromoteIndirect $ Outline.App $
outline
{ Outline._app_deps_direct = Map.insert pkg vsn direct
, Outline._app_deps_indirect = Map.delete pkg indirect
}
Nothing ->
-- is it already a test dependency?
case Map.lookup pkg testDirect of
Just vsn ->
return $ PromoteTest $ Outline.App $
outline
{ Outline._app_deps_direct = Map.insert pkg vsn direct
, Outline._app_test_direct = Map.delete pkg testDirect
}
Nothing ->
-- is it already an indirect test dependency?
case Map.lookup pkg testIndirect of
Just vsn ->
return $ PromoteTest $ Outline.App $
outline
{ Outline._app_deps_direct = Map.insert pkg vsn direct
, Outline._app_test_indirect = Map.delete pkg testIndirect
}
Nothing ->
-- finally try to add it from scratch
case Registry.getVersions' pkg registry of
Left suggestions ->
case connection of
Solver.Online _ -> Task.throw (Exit.InstallUnknownPackageOnline pkg suggestions)
Solver.Offline -> Task.throw (Exit.InstallUnknownPackageOffline pkg suggestions)
Right _ ->
do result <- Task.io $ Solver.addToApp cache connection registry pkg outline
case result of
Solver.Ok (Solver.AppSolution old new app) ->
return (Changes (detectChanges old new) (Outline.App app))
Solver.NoSolution ->
Task.throw (Exit.InstallNoOnlineAppSolution pkg)
Solver.NoOfflineSolution ->
Task.throw (Exit.InstallNoOfflineAppSolution pkg)
Solver.Err exit ->
Task.throw (Exit.InstallHadSolverTrouble exit)
-- MAKE PACKAGE PLAN
makePkgPlan :: Solver.Env -> Pkg.Name -> Outline.PkgOutline -> Task (Changes C.Constraint)
makePkgPlan (Solver.Env cache _ connection registry) pkg outline@(Outline.PkgOutline _ _ _ _ _ deps test _) =
if Map.member pkg deps then
return AlreadyInstalled
else
-- is already in test dependencies?
case Map.lookup pkg test of
Just con ->
return $ PromoteTest $ Outline.Pkg $
outline
{ Outline._pkg_deps = Map.insert pkg con deps
, Outline._pkg_test_deps = Map.delete pkg test
}
Nothing ->
-- try to add a new dependency
case Registry.getVersions' pkg registry of
Left suggestions ->
case connection of
Solver.Online _ -> Task.throw (Exit.InstallUnknownPackageOnline pkg suggestions)
Solver.Offline -> Task.throw (Exit.InstallUnknownPackageOffline pkg suggestions)
Right (Registry.KnownVersions _ _) ->
do let old = Map.union deps test
let cons = Map.insert pkg C.anything old
result <- Task.io $ Solver.verify cache connection registry cons
case result of
Solver.Ok solution ->
let
(Solver.Details vsn _) = solution ! pkg
con = C.untilNextMajor vsn
new = Map.insert pkg con old
changes = detectChanges old new
news = Map.mapMaybe keepNew changes
in
return $ Changes changes $ Outline.Pkg $
outline
{ Outline._pkg_deps = addNews (Just pkg) news deps
, Outline._pkg_test_deps = addNews Nothing news test
}
Solver.NoSolution ->
Task.throw (Exit.InstallNoOnlinePkgSolution pkg)
Solver.NoOfflineSolution ->
Task.throw (Exit.InstallNoOfflinePkgSolution pkg)
Solver.Err exit ->
Task.throw (Exit.InstallHadSolverTrouble exit)
addNews :: Maybe Pkg.Name -> Map.Map Pkg.Name C.Constraint -> Map.Map Pkg.Name C.Constraint -> Map.Map Pkg.Name C.Constraint
addNews pkg new old =
Map.merge
Map.preserveMissing
(Map.mapMaybeMissing (\k c -> if Just k == pkg then Just c else Nothing))
(Map.zipWithMatched (\_ _ n -> n))
old
new
-- CHANGES
data Change a
= Insert a
| Change a a
| Remove a
detectChanges :: (Eq a) => Map.Map Pkg.Name a -> Map.Map Pkg.Name a -> Map.Map Pkg.Name (Change a)
detectChanges old new =
Map.merge
(Map.mapMissing (\_ v -> Remove v))
(Map.mapMissing (\_ v -> Insert v))
(Map.zipWithMaybeMatched keepChange)
old
new
keepChange :: (Eq v) => k -> v -> v -> Maybe (Change v)
keepChange _ old new =
if old == new then
Nothing
else
Just (Change old new)
keepNew :: Change a -> Maybe a
keepNew change =
case change of
Insert a ->
Just a
Change _ a ->
Just a
Remove _ ->
Nothing
-- VIEW CHANGE DOCS
data ChangeDocs =
Docs
{ _doc_inserts :: [D.Doc]
, _doc_changes :: [D.Doc]
, _doc_removes :: [D.Doc]
}
viewChangeDocs :: ChangeDocs -> D.Doc
viewChangeDocs (Docs inserts changes removes) =
D.indent 2 $ D.vcat $ concat $
[ viewNonZero "Add:" inserts
, viewNonZero "Change:" changes
, viewNonZero "Remove:" removes
]
viewNonZero :: String -> [D.Doc] -> [D.Doc]
viewNonZero title entries =
if null entries then
[]
else
[ ""
, D.fromChars title
, D.indent 2 (D.vcat entries)
]
-- VIEW CHANGE
addChange :: (a -> String) -> Widths -> Pkg.Name -> Change a -> ChangeDocs -> ChangeDocs
addChange toChars widths name change (Docs inserts changes removes) =
case change of
Insert new ->
Docs (viewInsert toChars widths name new : inserts) changes removes
Change old new ->
Docs inserts (viewChange toChars widths name old new : changes) removes
Remove old ->
Docs inserts changes (viewRemove toChars widths name old : removes)
viewInsert :: (a -> String) -> Widths -> Pkg.Name -> a -> D.Doc
viewInsert toChars (Widths nameWidth leftWidth _) name new =
viewName nameWidth name <+> pad leftWidth (toChars new)
viewChange :: (a -> String) -> Widths -> Pkg.Name -> a -> a -> D.Doc
viewChange toChars (Widths nameWidth leftWidth rightWidth) name old new =
D.hsep
[ viewName nameWidth name
, pad leftWidth (toChars old)
, "=>"
, pad rightWidth (toChars new)
]
viewRemove :: (a -> String) -> Widths -> Pkg.Name -> a -> D.Doc
viewRemove toChars (Widths nameWidth leftWidth _) name old =
viewName nameWidth name <+> pad leftWidth (toChars old)
viewName :: Int -> Pkg.Name -> D.Doc
viewName width name =
D.fill (width + 3) (D.fromPackage name)
pad :: Int -> String -> D.Doc
pad width string =
D.fromChars (replicate (width - length string) ' ') <> D.fromChars string
-- WIDTHS
data Widths =
Widths
{ _name :: !Int
, _left :: !Int
, _right :: !Int
}
widen :: (a -> String) -> Pkg.Name -> Change a -> Widths -> Widths
widen toChars pkg change (Widths name left right) =
let
toLength a =
length (toChars a)
newName =
max name (length (Pkg.toChars pkg))
in
case change of
Insert new ->
Widths newName (max left (toLength new)) right
Change old new ->
Widths newName (max left (toLength old)) (max right (toLength new))
Remove old ->
Widths newName (max left (toLength old)) right
================================================
FILE: terminal/src/Main.hs
================================================
{-# LANGUAGE OverloadedStrings #-}
module Main
( main
)
where
import Prelude hiding (init)
import qualified Data.List as List
import qualified Text.PrettyPrint.ANSI.Leijen as P
import Text.Read (readMaybe)
import qualified Elm.Version as V
import Terminal
import Terminal.Helpers
import qualified Bump
import qualified Develop
import qualified Diff
import qualified Init
import qualified Install
import qualified Make
import qualified Publish
import qualified Repl
-- MAIN
main :: IO ()
main =
Terminal.app intro outro
[ repl
, init
, reactor
, make
, install
, bump
, diff
, publish
]
intro :: P.Doc
intro =
P.vcat
[ P.fillSep
["Hi,","thank","you","for","trying","out"
,P.green "Elm"
,P.green (P.text (V.toChars V.compiler)) <> "."
,"I hope you like it!"
]
, ""
, P.black "-------------------------------------------------------------------------------"
, P.black "I highly recommend working through Unexpected request format. This should not be possible!
\ \Please report this\ \ here\ \ along with the URL and your browser version.
" ignoreFile :: PartInfo -> Stream.InputStream B.ByteString -> Int -> IO Int ignoreFile _ _ count = return (count + 1) -- COMPILE data Outcome = Success N.Name B.Builder | NoMain | BadInput ModuleName.Raw Error.Error compile :: A.Artifacts -> B.ByteString -> Outcome compile (A.Artifacts interfaces objects) source = case Parse.fromByteString Parse.Application source of Left err -> BadInput N._Main (Error.BadSyntax err) Right modul@(Src.Module _ _ _ imports _ _ _ _ _) -> case checkImports interfaces imports of Left err -> BadInput (Src.getName modul) (Error.BadImports err) Right ifaces -> case Compile.compile Pkg.dummyName ifaces modul of Left err -> BadInput (Src.getName modul) err Right (Compile.Artifacts canModule _ locals) -> case locals of Opt.LocalGraph Nothing _ _ -> NoMain Opt.LocalGraph (Just main_) _ _ -> let mode = Mode.Dev Nothing home = Can._name canModule name = ModuleName._module home mains = Map.singleton home main_ graph = Opt.addLocalGraph locals objects in Success name $ JS.generate mode graph mains checkImports :: Map.Map ModuleName.Raw I.Interface -> [Src.Import] -> Either (NE.List Import.Error) (Map.Map ModuleName.Raw I.Interface) checkImports interfaces imports = let importDict = Map.fromValues Src.getImportName imports missing = Map.difference importDict interfaces in case Map.elems missing of [] -> Right (Map.intersection interfaces importDict) i:is -> let unimported = Map.keysSet (Map.difference interfaces importDict) toError (Src.Import (A.At region name) _ _) = Import.Error region name unimported Import.NotFound in Left (fmap toError (NE.List i is)) -- RENDER PROBLEM (V1) renderProblem_V1 :: Help.Report -> B.Builder renderProblem_V1 report = [r| |] -- RENDER SUCCESS (V2) renderSuccess_V2 :: N.Name -> B.Builder -> B.Builder renderSuccess_V2 moduleName javascript = let name = N.toBuilder moduleName in [r|