-
Notifications
You must be signed in to change notification settings - Fork 11
Repl API #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: lamdera-next
Are you sure you want to change the base?
Repl API #10
Changes from 22 commits
d8ae362
15a7069
c097d3e
b2a2e56
e0d55f0
7c8f310
8290dee
fa3cfe3
441f1ce
a27d4bb
d591f19
d1d0cd9
92c1c80
1e775be
9e57721
c9af64d
87727e6
e544b92
48def3e
3391d5f
b727101
8046918
3935f9d
effbe40
8a8a994
bdf9ffc
7d4f078
be46c86
ac87fe8
dc1fbe5
b422951
14b46e6
20bb71e
9173633
1505d77
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
:set -fbyte-code | ||
:set -fobject-code | ||
:set -fwarn-name-shadowing | ||
:def rr const $ return $ unlines ["Ext.Common.killTrackedThreads",":r","Test.target"] | ||
:def rr const $ return $ unlines ["Ext.Common.killTrackedThreads",":r","Test.target"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,3 +17,6 @@ extra/.cache | |
# @TESTS | ||
elm-home | ||
|
||
# Jim | ||
experimental/ | ||
.vscode/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
{-# OPTIONS_GHC -Wall #-} | ||
module Artifacts | ||
( Artifacts(..) | ||
, loadCompile | ||
, loadRepl | ||
, toDepsInfo | ||
) | ||
where | ||
|
||
|
||
import Control.Concurrent (readMVar) | ||
import Control.Monad (liftM2) | ||
import qualified Data.ByteString as BS | ||
import qualified Data.ByteString.Builder as B | ||
import qualified Data.ByteString.Lazy as LBS | ||
import qualified Data.Map as Map | ||
import qualified Data.Name as N | ||
import qualified Data.OneOrMore as OneOrMore | ||
import qualified System.Directory as Dir | ||
import System.FilePath ((</>)) | ||
|
||
import qualified AST.Canonical as Can | ||
import qualified AST.Optimized as Opt | ||
import qualified BackgroundWriter as BW | ||
import qualified Elm.Details as Details | ||
import qualified Elm.Interface as I | ||
import qualified Elm.ModuleName as ModuleName | ||
import qualified Elm.Package as Pkg | ||
import Json.Encode ((==>)) | ||
import qualified Json.Encode as E | ||
import qualified Json.String as Json | ||
import qualified Reporting | ||
|
||
|
||
|
||
-- ARTIFACTS | ||
|
||
|
||
data Artifacts = | ||
Artifacts | ||
{ _ifaces :: Map.Map ModuleName.Raw I.Interface | ||
, _graph :: Opt.GlobalGraph | ||
} | ||
|
||
|
||
loadCompile :: IO Artifacts | ||
loadCompile = | ||
load ("outlines" </> "compile") | ||
|
||
|
||
loadRepl :: IO Artifacts | ||
loadRepl = | ||
load ("outlines" </> "repl") | ||
|
||
|
||
|
||
-- LOAD | ||
|
||
|
||
load :: FilePath -> IO Artifacts | ||
load dir = | ||
BW.withScope $ \scope -> | ||
do putStrLn $ "Loading " ++ dir </> "elm.json" | ||
style <- Reporting.terminal | ||
root <- fmap (</> dir) Dir.getCurrentDirectory | ||
result <- Details.load style scope root | ||
case result of | ||
Left _ -> | ||
error $ "Ran into some problem loading elm.json\nTry running `lamdera make` in: " ++ dir | ||
|
||
Right details -> | ||
do omvar <- Details.loadObjects root details | ||
imvar <- Details.loadInterfaces root details | ||
mdeps <- readMVar imvar | ||
mobjs <- readMVar omvar | ||
case liftM2 (,) mdeps mobjs of | ||
Nothing -> | ||
error $ "Ran into some weird problem loading elm.json\nTry running `lamdera make` in: " ++ dir | ||
|
||
Just (deps, objs) -> | ||
return $ Artifacts (toInterfaces deps) objs | ||
|
||
|
||
toInterfaces :: Map.Map ModuleName.Canonical I.DependencyInterface -> Map.Map ModuleName.Raw I.Interface | ||
toInterfaces deps = | ||
Map.mapMaybe toUnique $ Map.fromListWith OneOrMore.more $ | ||
Map.elems (Map.mapMaybeWithKey getPublic deps) | ||
|
||
|
||
getPublic :: ModuleName.Canonical -> I.DependencyInterface -> Maybe (ModuleName.Raw, OneOrMore.OneOrMore I.Interface) | ||
getPublic (ModuleName.Canonical _ name) dep = | ||
case dep of | ||
I.Public iface -> Just (name, OneOrMore.one iface) | ||
I.Private _ _ _ -> Nothing | ||
|
||
|
||
toUnique :: OneOrMore.OneOrMore a -> Maybe a | ||
toUnique oneOrMore = | ||
case oneOrMore of | ||
OneOrMore.One value -> Just value | ||
OneOrMore.More _ _ -> Nothing | ||
|
||
|
||
|
||
-- TO DEPS INFO | ||
|
||
|
||
toDepsInfo :: Artifacts -> BS.ByteString | ||
toDepsInfo (Artifacts ifaces _) = | ||
LBS.toStrict $ B.toLazyByteString $ E.encodeUgly $ encode ifaces | ||
|
||
|
||
|
||
-- ENCODE | ||
|
||
|
||
encode :: Map.Map ModuleName.Raw I.Interface -> E.Value | ||
encode ifaces = | ||
E.dict Json.fromName encodeInterface ifaces | ||
|
||
|
||
encodeInterface :: I.Interface -> E.Value | ||
encodeInterface (I.Interface pkg values unions aliases binops) = | ||
E.object | ||
[ "pkg" ==> E.chars (Pkg.toChars pkg) | ||
, "ops" ==> E.list E.name (Map.keys binops) | ||
, "values" ==> E.list E.name (Map.keys values) | ||
, "aliases" ==> E.list E.name (Map.keys (Map.filter isPublicAlias aliases)) | ||
, "types" ==> E.dict Json.fromName (E.list E.name) (Map.mapMaybe toPublicUnion unions) | ||
] | ||
|
||
|
||
isPublicAlias :: I.Alias -> Bool | ||
isPublicAlias alias = | ||
case alias of | ||
I.PublicAlias _ -> True | ||
I.PrivateAlias _ -> False | ||
|
||
|
||
toPublicUnion :: I.Union -> Maybe [N.Name] | ||
toPublicUnion union = | ||
case union of | ||
I.OpenUnion (Can.Union _ variants _ _) -> | ||
Just (map getVariantName variants) | ||
|
||
I.ClosedUnion _ -> | ||
Just [] | ||
|
||
I.PrivateUnion _ -> | ||
Nothing | ||
|
||
|
||
getVariantName :: Can.Ctor -> N.Name | ||
getVariantName (Can.Ctor name _ _ _) = | ||
name |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
{-# OPTIONS_GHC -Wall #-} | ||
module Cors | ||
( allow | ||
) | ||
where | ||
|
||
|
||
import qualified Data.HashSet as HashSet | ||
import Network.URI (parseURI) | ||
import Snap.Core (Snap, Method, method) | ||
import Snap.Util.CORS (CORSOptions(..), HashableMethod(..), OriginList(Origins), applyCORS, mkOriginSet) | ||
|
||
|
||
|
||
-- ALLOW | ||
|
||
|
||
allow :: Method -> [String] -> Snap () -> Snap () | ||
allow method_ origins snap = | ||
applyCORS (toOptions method_ origins) $ method method_ $ | ||
snap | ||
|
||
|
||
|
||
-- TO OPTIONS | ||
|
||
|
||
toOptions :: (Monad m) => Method -> [String] -> CORSOptions m | ||
toOptions method_ origins = | ||
let | ||
allowedOrigins = toOriginList origins | ||
allowedMethods = HashSet.singleton (HashableMethod method_) | ||
in | ||
CORSOptions | ||
{ corsAllowOrigin = return allowedOrigins | ||
, corsAllowCredentials = return True | ||
, corsExposeHeaders = return HashSet.empty | ||
, corsAllowedMethods = return allowedMethods | ||
, corsAllowedHeaders = return | ||
} | ||
|
||
|
||
toOriginList :: [String] -> OriginList | ||
toOriginList origins = | ||
Origins $ mkOriginSet $ | ||
case traverse parseURI origins of | ||
Just uris -> uris | ||
Nothing -> error "invalid entry given to toOriginList list" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
{-# LANGUAGE OverloadedStrings #-} | ||
{-# LANGUAGE DeriveGeneric #-} | ||
|
||
module Endpoint.Package (handlePost, reportOnInstalledPackages) where | ||
|
||
import GHC.Generics (Generic) | ||
|
||
|
||
import Snap.Core | ||
import Snap.Http.Server | ||
import Data.Aeson (FromJSON, eitherDecode, encode, ToJSON, toJSON, object, (.=)) | ||
import Data.ByteString (ByteString) | ||
import Data.ByteString.Lazy (toStrict) | ||
import qualified Data.ByteString.Lazy.Char8 as BL | ||
import qualified Data.ByteString.Char8 as ByteString | ||
import GHC.Generics | ||
import System.IO (writeFile) | ||
import Control.Monad.IO.Class (liftIO) | ||
import qualified Data.Map as Map | ||
--- | ||
import Snap.Util.FileServe | ||
import qualified Data.ByteString.Lazy as LBS | ||
import qualified Data.HashMap.Strict as HM | ||
import Data.Text.Encoding (decodeUtf8) | ||
import Snap.Http.Server.Config (setPort, defaultConfig) | ||
import qualified Artifacts | ||
import Data.IORef | ||
|
||
|
||
data Package = Package { name :: String, version :: String } deriving (Show, Generic) | ||
|
||
instance FromJSON Package | ||
instance ToJSON Package | ||
|
||
|
||
type PackageList = [Package] | ||
|
||
writeElmJson :: PackageList -> IO () | ||
writeElmJson pkgs = do | ||
let directDeps = Map.fromList $ ("elm/core", "1.0.5"):[(name p, version p) | p <- pkgs] | ||
elmJson = object [ | ||
"type" .= ("application" :: String), | ||
"source-directories" .= (["../../repl-src"] :: [String]), | ||
"elm-version" .= ("0.19.1" :: String), | ||
"dependencies" .= object [ | ||
"direct" .= directDeps, | ||
"indirect" .= object [ | ||
"elm/json" .= ("1.1.3" :: String) | ||
] | ||
], | ||
"test-dependencies" .= object [ | ||
"direct" .= (Map.empty :: Map.Map String String), | ||
"indirect" .= (Map.empty :: Map.Map String String) | ||
] | ||
] | ||
writeFile "./outlines/repl/elm.json" ( BL.unpack $ encode elmJson) | ||
|
||
|
||
handlePost :: IORef Artifacts.Artifacts -> Snap () | ||
handlePost artifactRef = do | ||
body <- readRequestBody 10000 | ||
let maybePackageList = eitherDecode body :: Either String PackageList | ||
case maybePackageList of | ||
Left err -> writeBS $ "Error: Could not decode JSON: " <> (ByteString.pack err) | ||
Right packages -> do | ||
liftIO $ writeElmJson packages | ||
let message = ByteString.pack $ "Packages added: " ++ (show $ length packages) | ||
writeBS message | ||
newArtifacts <- liftIO Artifacts.loadRepl | ||
liftIO $ writeIORef artifactRef newArtifacts | ||
|
||
|
||
|
||
data Dependencies = Dependencies { | ||
direct :: HM.HashMap String String | ||
} deriving (Generic, Show) | ||
|
||
data TopLevel = TopLevel { | ||
dependencies :: Dependencies | ||
} deriving (Generic, Show) | ||
|
||
instance FromJSON TopLevel | ||
|
||
instance FromJSON Dependencies | ||
|
||
--- curl -X POST -H "Content-Length: 0" http://localhost:8000/reportOnInstalledPackages | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For testing the endpoint that returns a list of installed packages, for example: curl -X POST -H "Content-Length: 0" http://localhost:8000/reportOnInstalledPackages There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should I not add comments explaining the purpose of this file? |
||
reportOnInstalledPackages :: Snap () | ||
reportOnInstalledPackages = do | ||
jsonData <- liftIO $ LBS.readFile "./outlines/repl/elm.json" | ||
case eitherDecode jsonData :: Either String TopLevel of | ||
Left err -> writeBS $ "Failed to parse JSON: " <> (LBS.toStrict jsonData) | ||
Right topLevel -> do | ||
let directDeps = HM.toList $ direct $ dependencies topLevel | ||
let outputList = map (\(name, version) -> object ["name" .= name, "version" .= version]) directDeps | ||
writeBS . LBS.toStrict . encode $ outputList |
Uh oh!
There was an error while loading. Please reload this page.