on master help sign in
Adopt "model" code from elm-get so that dependencies can be crawled from the compiler.
commit3c1b9f7171cb2e8fdb805bbf7b4cf7323f3a15dd authorEvan Czaplicki <info@elm-lang.org> dateSun 15 Dec 2013, 23:55 (13 years ago) parent61ff490
8 files changed+286−48
Elm.cabal+12−3 compiler/Build/Dependencies.hs+4−29 compiler/Elm/Internal/Dependencies.hs+93 compiler/Elm/Internal/Documentation.hs+41 compiler/Elm/Internal/Name.hs+48 compiler/Elm/Internal/Paths.hs+23 compiler/Language/Elm.hs → compiler/Elm/Internal/Utils.hs+2−16 compiler/Elm/Internal/Version.hs+63
Elm.cabal+12−3
@@ -30,7 +30,12 @@ source-repository head
3030 location: git://github.com/evancz/Elm.git
3131
3232Library
33− exposed-modules: Language.Elm
33+ exposed-modules: Elm.Internal.Dependencies,
34+ Elm.Internal.Documentation,
35+ Elm.Internal.Name,
36+ Elm.Internal.Paths,
37+ Elm.Internal.Utils,
38+ Elm.Internal.Version
3439 Hs-Source-Dirs: compiler
3540 other-modules: SourceSyntax.Declaration,
3641 SourceSyntax.Expression,
@@ -86,7 +91,8 @@ Library
8691 Build.Utils,
8792 Paths_Elm
8893
89− Build-depends: base >=4.2 && <5,
94+ Build-depends: aeson,
95+ base >=4.2 && <5,
9096 binary >= 0.6.4.0,
9197 blaze-html == 0.5.* || == 0.6.*,
9298 blaze-markup,
@@ -101,6 +107,7 @@ Library
101107 pandoc >= 1.10,
102108 parsec >= 3.1.1,
103109 pretty,
110+ text,
104111 transformers >= 0.2,
105112 union-find,
106113 uniplate
@@ -163,7 +170,8 @@ Executable elm
163170 Build.Utils,
164171 Paths_Elm
165172
166− Build-depends: base >=4.2 && <5,
173+ Build-depends: aeson,
174+ base >=4.2 && <5,
167175 binary >= 0.6.4.0,
168176 blaze-html == 0.5.* || == 0.6.*,
169177 blaze-markup == 0.5.1.*,
@@ -178,6 +186,7 @@ Executable elm
178186 pandoc >= 1.10,
179187 parsec >= 3.1.1,
180188 pretty,
189+ text,
181190 transformers >= 0.2,
182191 union-find,
183192 uniplate
compiler/Build/Dependencies.hs+4−29
@@ -24,6 +24,7 @@ import qualified Transform.SortDefinitions as SD
2424import qualified Type.Inference as TI
2525import qualified Type.Constrain.Declaration as TcDecl
2626import qualified Transform.Canonicalize as Canonical
27+import qualified Elm.Internal.Version as V
2728
2829getSortedDependencies :: [FilePath] -> Interfaces -> FilePath -> IO [String]
2930getSortedDependencies srcDirs builtIns root =
@@ -44,41 +45,15 @@ additionalSourceDirs =
4445
4546 getLatest project = do
4647 subs <- getDirectoryContents project
47− case Maybe.mapMaybe version subs of
48+ case Maybe.mapMaybe V.fromString subs of
4849 [] -> return Nothing
49− versions -> return $ Just $ (project </>) $ showVersion maxVersion
50− where maxVersion = List.maximumBy compareVersion versions
50+ versions -> return $ Just $ (project </>) $ show maxVersion
51+ where maxVersion = List.maximum versions
5152
5253 isProject path = do
5354 exists <- doesDirectoryExist path
5455 return $ exists && path `notElem` [".","..","_internals"]
5556
56−showVersion (ns,tag) =
57− List.intercalate "." (map show ns) ++ if null tag then "" else "-" ++ tag
58−
59−compareVersion a b =
60− case compare (fst a) (fst b) of
61− EQ -> compare (snd b) (snd a) -- reverse comparison to favor ""
62− cmp -> cmp
63−
64−version :: String -> Maybe ([Int], String)
65−version version = (,) <$> splitNumbers possibleNumbers <*> tag
66− where
67− (possibleNumbers, possibleTag) = break (=='-') version
68−
69− tag = case possibleTag of
70− "" -> Just ""
71− '-':rest -> Just rest
72− _ -> Nothing
73−
74− splitNumbers :: String -> Maybe [Int]
75− splitNumbers ns =
76− case span Char.isDigit ns of
77− ("", _) -> Nothing
78− (number, []) -> Just [read number]
79− (number, '.':rest) -> (read number :) <$> splitNumbers rest
80− _ -> Nothing
81−
8257type Deps = (FilePath, String, [String])
8358
8459sortDeps :: [Deps] -> IO [String]
compiler/Elm/Internal/Dependencies.hsnew file+93
@@ -0,0 +1,93 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Elm.Internal.Dependencies where
3+
4+import Control.Applicative
5+import Control.Monad.Error
6+import qualified Control.Exception as E
7+import Data.Aeson
8+import qualified Data.List as List
9+import qualified Data.ByteString.Lazy as BS
10+import qualified Data.Map as Map
11+import qualified Elm.Internal.Name as N
12+import qualified Elm.Internal.Version as V
13+
14+data Deps = Deps
15+ { name :: N.Name
16+ , version :: V.Version
17+ , summary :: String
18+ , description :: String
19+ , license :: String
20+ , repo :: String
21+ , exposed :: [String]
22+ , elmVersion :: V.Version
23+ } deriving Show
24+
25+instance FromJSON Deps where
26+ parseJSON (Object obj) =
27+ do version <- obj .: "version"
28+
29+ summary <- obj .: "summary"
30+ when (length summary >= 80) $
31+ fail "'summary' must be less than 80 characters"
32+
33+ desc <- obj .: "description"
34+ license <- obj .: "license"
35+
36+ repo <- obj .: "repository"
37+ name <- case repoToName repo of
38+ Left err -> fail err
39+ Right nm -> return nm
40+
41+ exposed <- obj .: "exposed-modules"
42+ when (null exposed) $
43+ fail "there are no 'exposed-modules'.\n\
44+ \At least one module must be exposed for anyone to use this library!"
45+
46+ elmVersion <- obj .: "elm-version"
47+
48+ return $ Deps name version summary desc license repo exposed elmVersion
49+
50+ parseJSON _ = mzero
51+
52+repoToName :: String -> Either String N.Name
53+repoToName repo
54+ | not (end `List.isSuffixOf` repo) = Left msg
55+ | otherwise =
56+ do path <- getPath
57+ let raw = take (length path - length end) path
58+ case N.fromString raw of
59+ Nothing -> Left msg
60+ Just name -> Right name
61+ where
62+ getPath | http `List.isPrefixOf` repo = Right $ drop (length http ) repo
63+ | https `List.isPrefixOf` repo = Right $ drop (length https) repo
64+ | otherwise = Left msg
65+ http = "http://github.com/"
66+ https = "https://github.com/"
67+ end = ".git"
68+ msg = "the 'repository' field must point to a GitHub project for now, something\n\
69+ \like <https://github.com/USER/PROJECT.git> where USER is your GitHub name\n\
70+ \and PROJECT is the repo you want to upload."
71+
72+withDeps :: (Deps -> a) -> FilePath -> ErrorT String IO a
73+withDeps handle path =
74+ do json <- readPath
75+ case eitherDecode json of
76+ Left err -> throwError $ "Error reading file " ++ path ++ ":\n " ++ err
77+ Right ds -> return (handle ds)
78+ where
79+ readPath :: ErrorT String IO BS.ByteString
80+ readPath = do
81+ result <- liftIO $ E.catch (Right <$> BS.readFile path)
82+ (\err -> return $ Left (err :: IOError))
83+ case result of
84+ Right bytes -> return bytes
85+ Left _ -> throwError $
86+ "could not find file " ++ path ++
87+ "\n You may need to create a dependency file for your project."
88+
89+dependencies :: FilePath -> ErrorT String IO (Map.Map String String)
90+dependencies _ = return Map.empty -- withDeps deps
91+
92+depsAt :: FilePath -> ErrorT String IO Deps
93+depsAt = withDeps id
compiler/Elm/Internal/Documentation.hsnew file+41
@@ -0,0 +1,41 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Elm.Internal.Documentation where
3+
4+import Control.Applicative ((<$>),(<*>))
5+import Control.Monad
6+import Data.Aeson
7+
8+data Document = Doc
9+ { moduleName :: String
10+ , structure :: String
11+ , entries :: [Entry]
12+ } deriving (Show)
13+
14+data Entry = Entry
15+ { name :: String
16+ , comment :: String
17+ , raw :: String
18+ , assocPrec :: Maybe (String,Int)
19+ } deriving (Show)
20+
21+instance FromJSON Document where
22+ parseJSON (Object v) =
23+ Doc <$> v .: "name"
24+ <*> v .: "document"
25+ <*> (concat <$> sequence [ v .: "aliases", v .: "datatypes", v .: "values" ])
26+
27+ parseJSON _ = fail "Conversion to Document was expecting an object"
28+
29+instance FromJSON Entry where
30+ parseJSON (Object v) =
31+ Entry <$> v .: "name"
32+ <*> v .: "comment"
33+ <*> v .: "raw"
34+ <*> (liftM2 (,) <$> v .:? "associativity"
35+ <*> v .:? "precedence")
36+
37+ parseJSON _ = fail "Conversion to Entry was expecting an object"
38+
39+data Content = Markdown String | Value String
40+ deriving Show
41+
compiler/Elm/Internal/Name.hsnew file+48
@@ -0,0 +1,48 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+module Elm.Internal.Name where
3+
4+import Control.Applicative
5+import Control.Monad.Error
6+import Data.Aeson
7+import Data.Binary
8+import qualified Data.Text as T
9+import qualified Data.Maybe as Maybe
10+import Data.Typeable
11+
12+data Name = Name { user :: String, project :: String }
13+ deriving (Typeable,Eq)
14+
15+instance Binary Name where
16+ get = Name <$> get <*> get
17+ put (Name user project) =
18+ put user >> put project
19+
20+instance Show Name where
21+ show name = user name ++ "/" ++ project name
22+
23+toFilePath :: Name -> FilePath
24+toFilePath name = user name ++ "-" ++ project name
25+
26+fromString :: String -> Maybe Name
27+fromString string =
28+ case break (=='/') string of
29+ ( user@(_:_), '/' : project@(_:_) )
30+ | all (/='/') project -> Just (Name user project)
31+ _ -> Nothing
32+
33+fromString' :: String -> ErrorT String IO Name
34+fromString' string =
35+ Maybe.maybe (throwError $ errorMsg string) return (fromString string)
36+
37+instance FromJSON Name where
38+ parseJSON (String text) =
39+ let string = T.unpack text in
40+ Maybe.maybe (fail $ errorMsg string) return (fromString string)
41+
42+ parseJSON _ = fail "Project name must be a string."
43+
44+errorMsg string =
45+ unlines
46+ [ "Dependency file has an invalid name: " ++ string
47+ , "Must have format user/project and match a public github project."
48+ ]
compiler/Elm/Internal/Paths.hsnew file+23
@@ -0,0 +1,23 @@
1+module Elm.Internal.Paths where
2+
3+import System.IO.Unsafe
4+import qualified Paths_Elm as This
5+
6+-- |Name of directory for all of a project's dependencies.
7+dependencyDirectory :: FilePath
8+dependencyDirectory = "elm_dependencies"
9+
10+-- |Name of the dependency file, specifying dependencies and
11+-- other metadata for building and sharing projects.
12+dependencyFile :: FilePath
13+dependencyFile = "elm_dependencies.json"
14+
15+{-# NOINLINE runtime #-}
16+-- |The absolute path to Elm's runtime system.
17+runtime :: FilePath
18+runtime = unsafePerformIO $ This.getDataFileName "elm-runtime.js"
19+
20+{-# NOINLINE docs #-}
21+-- |The absolute path to Elm's core library documentation.
22+docs :: FilePath
23+docs = unsafePerformIO $ This.getDataFileName "docs.json"
compiler/Language/Elm.hs → compiler/Elm/Internal/Utils.hsrenamed+2−16
@@ -1,11 +1,6 @@
1−{- | This module exports functions for compiling Elm to JS
2− and some utilities for making it easier to find some Elm-related files.
3−
4− Learning resources for Elm are available at
5− <http://elm-lang.org.elm/Learn.elm> and many interactive examples are
6− available at <http://elm-lang.org/Examples.elm>
1+{- | This module exports functions for compiling Elm to JS.
72-}
8−module Language.Elm (compile, moduleName, runtime, docs) where
3+module Elm.Internal.Utils (compile, moduleName) where
94
105import qualified Data.List as List
116import qualified Generate.JavaScript as JS
@@ -14,7 +9,6 @@ import qualified Parse.Module as Parser
149import qualified SourceSyntax.Module as M
1510import qualified Text.PrettyPrint as P
1611import qualified Metadata.Prelude as Prelude
17−import qualified Paths_Elm as This
1812import System.IO.Unsafe
1913
2014-- |This function compiles Elm code to JavaScript. It will return either
@@ -32,11 +26,3 @@ interfaces = unsafePerformIO $ Prelude.interfaces
3226-- |This function extracts the module name of a given source program.
3327moduleName :: String -> Maybe String
3428moduleName = Parser.getModuleName
35−
36−-- |The absolute path to Elm's runtime system.
37−runtime :: IO FilePath
38−runtime = This.getDataFileName "elm-runtime.js"
39−
40−-- |The absolute path to Elm's core library documentation.
41−docs :: IO FilePath
42−docs = This.getDataFileName "docs.json"
compiler/Elm/Internal/Version.hsnew file+63
@@ -0,0 +1,63 @@
1+{-# LANGUAGE DeriveDataTypeable, TemplateHaskell #-}
2+module Elm.Internal.Version where
3+
4+import Control.Applicative
5+import Data.Aeson
6+import Data.Binary
7+import Data.Char (isDigit)
8+import qualified Data.List as List
9+import Data.Typeable
10+import qualified Data.Text as T
11+
12+-- Data representation
13+
14+data Version = V [Int] String
15+ deriving (Typeable,Eq)
16+
17+instance Ord Version where
18+ compare (V ns tag) (V ns' tag') =
19+ case compare ns ns' of
20+ EQ -> compare tag' tag -- reverse comparison to favor ""
21+ cmp -> cmp
22+
23+instance Show Version where
24+ show (V ns tag) =
25+ List.intercalate "." (map show ns) ++ if null tag then "" else "-" ++ tag
26+
27+instance Binary Version where
28+ get = V <$> get <*> get
29+ put (V ns tag) = do put ns
30+ put tag
31+
32+tagless :: Version -> Bool
33+tagless (V _ tag) = null tag
34+
35+fromString :: String -> Maybe Version
36+fromString version = V <$> splitNumbers possibleNumbers <*> tag
37+ where
38+ (possibleNumbers, possibleTag) = break (=='-') version
39+
40+ tag = case possibleTag of
41+ "" -> Just ""
42+ '-':rest -> Just rest
43+ _ -> Nothing
44+
45+ splitNumbers :: String -> Maybe [Int]
46+ splitNumbers ns =
47+ case span isDigit ns of
48+ ("", _) -> Nothing
49+ (number, []) -> Just [read number]
50+ (number, '.':rest) -> (read number :) <$> splitNumbers rest
51+ _ -> Nothing
52+
53+instance FromJSON Version where
54+ parseJSON (String text) =
55+ let string = T.unpack text in
56+ case fromString string of
57+ Just v -> return v
58+ Nothing -> fail $ unlines
59+ [ "Dependency file has an invalid version number: " ++ string
60+ , "Must have format 0.1.2 or 0.1.2-tag"
61+ ]
62+
63+ parseJSON _ = fail "Version number must be stored as a string."