move things around, begin work on cli

This commit is contained in:
Ryan Haskell-Glatz 2019-11-12 16:27:56 -08:00
parent 5319cb1847
commit 50b9c78fd9
76 changed files with 955 additions and 118 deletions

5
.gitignore vendored
View File

@ -1,5 +1,4 @@
.DS_Store .DS_Store
dist ./dist
elm-stuff/0.19.1 elm-stuff/0.19.1
example/elm-stuff/0.19.1 ./node_modules
node_modules

3
cli/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
dist
elm-stuff
node_modules

View File

@ -1,4 +1,4 @@
# cli # elm-spa/cli
> the thing that types the stuff > the thing that types the stuff
__Note:__ I will not implement this until I understand __Note:__ I will not implement this until I understand

24
cli/elm.json Normal file
View File

@ -0,0 +1,24 @@
{
"type": "application",
"source-directories": [
"src/elm"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"elm/browser": "1.0.2",
"elm/core": "1.0.2",
"elm/html": "1.0.0",
"elm/json": "1.1.3"
},
"indirect": {
"elm/time": "1.0.0",
"elm/url": "1.0.0",
"elm/virtual-dom": "1.0.2"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}

403
cli/package-lock.json generated Normal file
View File

@ -0,0 +1,403 @@
{
"name": "elm-spa",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"ajv": {
"version": "6.10.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
"integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
"dev": true,
"requires": {
"fast-deep-equal": "^2.0.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"asn1": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
"integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
"dev": true,
"requires": {
"safer-buffer": "~2.1.0"
}
},
"assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
"dev": true
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
"dev": true
},
"aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
"dev": true
},
"aws4": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
"dev": true
},
"bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
"dev": true,
"requires": {
"tweetnacl": "^0.14.3"
}
},
"caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
"dev": true
},
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"requires": {
"delayed-stream": "~1.0.0"
}
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true
},
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
"dev": true,
"requires": {
"assert-plus": "^1.0.0"
}
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
"dev": true
},
"ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
"dev": true,
"requires": {
"jsbn": "~0.1.0",
"safer-buffer": "^2.1.0"
}
},
"elm": {
"version": "0.19.1-3",
"resolved": "https://registry.npmjs.org/elm/-/elm-0.19.1-3.tgz",
"integrity": "sha512-6y36ewCcVmTOx8lj7cKJs3bhI5qMfoVEigePZ9PhEUNKpwjjML/pU2u2YSpHVAznuCcojoF6KIsrS1Ci7GtVaQ==",
"dev": true,
"requires": {
"request": "^2.88.0"
}
},
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"dev": true
},
"extsprintf": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
"dev": true
},
"fast-deep-equal": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
"dev": true
},
"fast-json-stable-stringify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
"dev": true
},
"forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
"dev": true
},
"form-data": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"dev": true,
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
"mime-types": "^2.1.12"
}
},
"getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
"dev": true,
"requires": {
"assert-plus": "^1.0.0"
}
},
"har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
"dev": true
},
"har-validator": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
"dev": true,
"requires": {
"ajv": "^6.5.5",
"har-schema": "^2.0.0"
}
},
"http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
"dev": true,
"requires": {
"assert-plus": "^1.0.0",
"jsprim": "^1.2.2",
"sshpk": "^1.7.0"
}
},
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
"dev": true
},
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
"dev": true
},
"jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
"dev": true
},
"json-schema": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
"dev": true
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
"dev": true
},
"jsprim": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
"dev": true,
"requires": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
"json-schema": "0.2.3",
"verror": "1.10.0"
}
},
"mime-db": {
"version": "1.42.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz",
"integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==",
"dev": true
},
"mime-types": {
"version": "2.1.25",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz",
"integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==",
"dev": true,
"requires": {
"mime-db": "1.42.0"
}
},
"oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
"dev": true
},
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
"dev": true
},
"psl": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz",
"integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==",
"dev": true
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"dev": true
},
"qs": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
"dev": true
},
"request": {
"version": "2.88.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
"dev": true,
"requires": {
"aws-sign2": "~0.7.0",
"aws4": "^1.8.0",
"caseless": "~0.12.0",
"combined-stream": "~1.0.6",
"extend": "~3.0.2",
"forever-agent": "~0.6.1",
"form-data": "~2.3.2",
"har-validator": "~5.1.0",
"http-signature": "~1.2.0",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
"json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.19",
"oauth-sign": "~0.9.0",
"performance-now": "^2.1.0",
"qs": "~6.5.2",
"safe-buffer": "^5.1.2",
"tough-cookie": "~2.4.3",
"tunnel-agent": "^0.6.0",
"uuid": "^3.3.2"
}
},
"safe-buffer": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
"integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
"sshpk": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
"integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
"dev": true,
"requires": {
"asn1": "~0.2.3",
"assert-plus": "^1.0.0",
"bcrypt-pbkdf": "^1.0.0",
"dashdash": "^1.12.0",
"ecc-jsbn": "~0.1.1",
"getpass": "^0.1.1",
"jsbn": "~0.1.0",
"safer-buffer": "^2.0.2",
"tweetnacl": "~0.14.0"
}
},
"tough-cookie": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
"dev": true,
"requires": {
"psl": "^1.1.24",
"punycode": "^1.4.1"
},
"dependencies": {
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
"dev": true
}
}
},
"tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
"dev": true,
"requires": {
"safe-buffer": "^5.0.1"
}
},
"tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"dev": true
},
"uri-js": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
"integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
"dev": true,
"requires": {
"punycode": "^2.1.0"
}
},
"uuid": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==",
"dev": true
},
"verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
"dev": true,
"requires": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
}
}
}
}

27
cli/package.json Normal file
View File

@ -0,0 +1,27 @@
{
"name": "elm-spa",
"version": "1.0.0",
"description": "the cli companion tool for ryannhg/elm-spa",
"main": "src/index.js",
"scripts": {
"start": "npm run build && node src/index.js",
"build": "npm run elm:compile",
"elm:compile": "elm make src/elm/Main.elm --output=dist/elm.compiled.js --optimize"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ryannhg/elm-spa.git"
},
"keywords": [
"elm",
"spa",
"cli",
"framework"
],
"author": "Ryan Haskell-Glatz",
"license": "ISC",
"bugs": {
"url": "https://github.com/ryannhg/elm-spa/issues"
},
"homepage": "https://github.com/ryannhg/elm-spa#readme"
}

106
cli/src/elm/File.elm Normal file
View File

@ -0,0 +1,106 @@
module File exposing
( File
, encode
, params
)
import Json.Encode as Json
type alias File =
{ filepath : List String
, contents : String
}
encode : File -> Json.Value
encode file =
Json.object
[ ( "filepath", Json.list Json.string file.filepath )
, ( "contents", Json.string file.contents )
]
-- PARAMS TEMPLATE
params :
{ moduleName : String
, paths : List (List String)
}
-> String
params options =
"""
module {{moduleName}} exposing (..)
{{paramsTypeAliases}}
"""
|> String.replace "{{moduleName}}"
(paramsModuleName options.moduleName)
|> String.replace "{{paramsTypeAliases}}"
(paramsTypeAliases options.paths)
|> String.trim
paramsModuleName : String -> String
paramsModuleName name =
[ "Generated", name, "Params" ]
|> List.filter (String.isEmpty >> not)
|> String.join "."
paramsTypeAliases : List (List String) -> String
paramsTypeAliases =
List.map paramsTypeAlias >> String.join "\n\n\n"
paramsTypeAlias : List String -> String
paramsTypeAlias filepath =
"""
type alias {{last}} =
{{paramsRecord}}
"""
|> String.replace "{{last}}"
(last filepath |> Maybe.withDefault "")
|> String.replace "{{paramsRecord}}"
(paramsRecord filepath |> indent 1)
|> String.trim
paramsRecord : List String -> String
paramsRecord path =
let
dynamicCount =
path
|> List.filter ((==) "Dynamic")
|> List.length
in
if dynamicCount < 1 then
"{}"
else
List.range 1 dynamicCount
|> List.map String.fromInt
|> List.map (\num -> [ "param", num, " : String" ])
|> List.map String.concat
|> String.join "\n, "
|> (\str -> "{ " ++ str ++ "\n}")
-- UTILS
last : List a -> Maybe a
last list =
List.drop (List.length list - 1) list |> List.head
indent : Int -> String -> String
indent tabs str =
str
|> String.split "\n"
|> List.map (\s -> String.concat (List.repeat tabs " " ++ [ s ]))
|> String.join "\n"

77
cli/src/elm/Main.elm Normal file
View File

@ -0,0 +1,77 @@
module Main exposing (main)
import Dict exposing (Dict)
import File exposing (File)
import Json.Encode as Json
import Ports
type alias Flags =
List Filepath
type alias Filepath =
List String
main : Program Flags () Never
main =
Platform.worker
{ init = \json -> ( (), parse json )
, update = \_ model -> ( model, Cmd.none )
, subscriptions = always Sub.none
}
-- ACTUAL CODE
parse : List Filepath -> Cmd msg
parse =
List.foldl groupByFolder Dict.empty
>> (\dict ->
[ paramsFiles dict
]
)
>> List.concat
>> Ports.sendFiles
folderOf : Filepath -> String
folderOf =
List.reverse
>> List.drop 1
>> List.reverse
>> String.join "."
groupByFolder :
Filepath
-> Dict String (List Filepath)
-> Dict String (List Filepath)
groupByFolder items =
Dict.update
(folderOf items)
(Maybe.map ((::) items)
>> Maybe.withDefault [ items ]
>> Just
)
paramsFiles :
Dict String (List Filepath)
-> List File
paramsFiles =
Dict.toList
>> List.map
(\( moduleName, paths ) ->
{ filepath =
String.split "." moduleName ++ [ "Params" ]
, contents =
File.params
{ moduleName = moduleName
, paths = paths
}
}
)

19
cli/src/elm/Ports.elm Normal file
View File

@ -0,0 +1,19 @@
port module Ports exposing (sendFiles)
import File exposing (File)
import Json.Encode as Json
port outgoing :
{ message : String
, data : Json.Value
}
-> Cmd msg
sendFiles : List File -> Cmd msg
sendFiles files =
outgoing
{ message = "sendFiles"
, data = Json.list File.encode files
}

11
cli/src/index.js Normal file
View File

@ -0,0 +1,11 @@
const path = require('path')
const cwd = process.cwd()
const { File, Elm } = require('./utils.js')
const start = () =>
File.paths(path.join(cwd, '..', 'examples', 'complex', 'src', 'Pages'))
.then(Elm.run)
.then(console.info)
.catch(console.error)
start(process.argv.slice(2))

87
cli/src/utils.js Normal file
View File

@ -0,0 +1,87 @@
const File = (_ => {
const path = require('path')
const fs = require('fs')
const mkdir = (filepath) =>
new Promise((resolve, reject) =>
fs.mkdir(filepath, { recursive: true }, (err) => err ? reject(err) : resolve(filepath))
)
const create = (filepath, contents) => {
const write = (filepath, contents) =>
new Promise((resolve, reject) =>
fs.writeFile(filepath, contents, { encoding: 'utf8' }, (err) =>
err ? reject(err) : resolve(filepath)
)
)
return fs.existsSync(folderOf(filepath))
? write(filepath, contents)
: mkdir(folderOf(filepath)).then(_ => write(filepath, contents))
}
const paths = (filepath) => {
const ls = (filepath) =>
new Promise((resolve, reject) =>
fs.readdir(filepath, (err, files) =>
err ? reject(err) : resolve(files))
)
const reduce = (fn, init) => (items) =>
items.reduce(fn, init)
const concat = (a, b) =>
a.concat(b)
const all = (fn) => (items) =>
Promise.all(items.map(fn))
const isFile = (name) =>
name.indexOf('.') > -1
const toPath = (filepath) => (name) =>
isFile(name)
? Promise.resolve([[ name.split('.')[0] ]])
: paths(path.join(filepath, name))
.then(files => files.map(file => [ name ].concat(file)))
return ls(filepath)
.then(all(toPath(filepath)))
.then(reduce(concat, []))
}
return {
paths,
mkdir,
create
}
})()
const Elm = (_ => {
const { Elm } = require('../dist/elm.compiled.js')
const handlers = {
sendFiles: (data) =>
data.forEach(a => console.log(a.filepath) || console.log(a.contents))
}
const run = paths =>
new Promise((resolve, reject) => {
const app = Elm.Main.init({ flags: paths })
app.ports.outgoing.subscribe(({ message, data }) =>
handlers[message]
? Promise.resolve(handlers[message](data)).then(resolve).catch(reject)
: reject(`Didn't recognize message "${message}" Yell at @ryannhg on the internet!\n`)
)
})
return {
run
}
})()
module.exports = {
Elm,
File
}

View File

@ -1,10 +0,0 @@
# elm-spa/example
> what a project might look like!
## running things
```
npm install
npm run dev
```

View File

@ -1,6 +0,0 @@
# elm-stuff/.elm-spa
> this is where all the generated code goes!
Normally, this whole directory should be `.gitignore`d,
but I'm keeping it around so I can better understand
what `elm-spa build` should be generating!

10
examples/README.md Normal file
View File

@ -0,0 +1,10 @@
# elm-spa/examples
> example projects using elm-spa
### how do I get started?
Check out the [intro example](./intro) to see what a project looks like.
### how does elm-spa handle XYZ?
There's a more [complex example](./complex) to answer those questions.

View File

@ -1,3 +1,4 @@
.DS_Store
dist dist
elm-stuff/0.19.1 elm-stuff/0.19.1
node_modules node_modules

View File

@ -0,0 +1,10 @@
# elm-spa/examples/complex
> an example of using more complex features
## running things
```
npm install
npm run dev
```

View File

@ -0,0 +1,6 @@
# elm-stuff/.elm-spa
> this is where all the generated code goes!
Normally, this whole directory should __not__ be in `git`,
but I'm keeping it around for now to better understand
all the edge cases that the CLI should generate!

View File

@ -2,7 +2,7 @@
"type": "application", "type": "application",
"source-directories": [ "source-directories": [
"src", "src",
"../src", "../../src",
"elm-stuff/.elm-spa" "elm-stuff/.elm-spa"
], ],
"elm-version": "0.19.1", "elm-version": "0.19.1",

View File

@ -1,5 +1,5 @@
{ {
"name": "elm-spa-elm-ui", "name": "complex-elm-spa-example",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,

View File

@ -1,5 +1,5 @@
{ {
"name": "your-elm-spa", "name": "complex-elm-spa-example",
"version": "1.0.0", "version": "1.0.0",
"description": "an example for elm-spa!", "description": "an example for elm-spa!",
"main": "src/index.js", "main": "src/index.js",

View File

@ -34,18 +34,23 @@ type alias Bundle msg appMsg =
App.Types.Bundle msg (Element msg) Global.Model Global.Msg appMsg (Element appMsg) App.Types.Bundle msg (Element msg) Global.Model Global.Msg appMsg (Element appMsg)
layout config = type alias Layout params model msg appMsg =
App.Page.layout App.Types.Layout params model msg (Element msg) Global.Model Global.Msg appMsg (Element appMsg)
{ map = Element.map
, view = config.view
, recipe = config.recipe
}
recipe config = type alias Upgrade params model msg layoutModel layoutMsg appMsg =
App.Page.recipe App.Types.Upgrade params model msg (Element msg) layoutModel layoutMsg (Element layoutMsg) Global.Model Global.Msg appMsg (Element appMsg)
{ map = Element.map
, page = config.page
, toModel = config.toModel layout :
, toMsg = config.toMsg Layout params model msg appMsg
} -> Page params model msg layoutModel layoutMsg appMsg
layout =
App.Page.layout Element.map
recipe :
Upgrade params model msg layoutModel layoutMsg appMsg
-> Recipe params model msg layoutModel layoutMsg appMsg
recipe =
App.Page.recipe Element.map

View File

@ -2,11 +2,14 @@
"name": "elm-spa", "name": "elm-spa",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "src/index.js", "main": "cli/src/index.js",
"scripts": { "scripts": {
"start": "npm run dev", "start": "npm run dev",
"example": "npm run dev", "build": "(cd cli && npm run build)",
"dev": "(cd example && npm install && npm run dev)" "dev": "npm run example",
"example": "npm run examples:complex",
"examples:intro": "(cd examples/intro && npm install && npm run dev)",
"examples:complex": "(cd examples/complex && npm install && npm run dev)"
}, },
"dependencies": {}, "dependencies": {},
"devDependencies": {}, "devDependencies": {},

View File

@ -14,12 +14,10 @@ as the entrypoint to your app.
module Main exposing (main) module Main exposing (main)
import App import App
import Element
import Global import Global
import Pages import Pages
import Routes import Routes
main : App.Program Global.Flags Global.Model Global.Msg Pages.Model Pages.Msg
main = main =
App.create App.create
{ ui = App.usingHtml { ui = App.usingHtml
@ -140,11 +138,10 @@ create :
create config = create config =
let let
page = page =
Page.upgrade Page.upgrade (always identity)
{ toModel = identity { toModel = identity
, toMsg = identity , toMsg = identity
, page = config.page , page = config.page
, map = always identity
} }
in in
Browser.application Browser.application

View File

@ -61,36 +61,11 @@ these are for!
## layout ## layout
A page that is comprimised of smaller pages, that is
able to share a common layout (maybe a something like a sidebar!)
page =
Page.layout
{ map = Html.map
, layout = Layout.view
, pages =
{ init = init
, update = update
, bundle = bundle
}
}
@docs layout @docs layout
## recipe ## recipe
Implementing the `init`, `update` and `bundle` functions is much easier
when you turn a `Page` type into `Recipe`.
A `Recipe` contains a record waiting for page specific data.
- `init`: just needs a `route`
- `upgrade` : just needs a `msg` and `model`
- `bundle` (`view`/`subscriptions`) : just needs a `model`
@docs recipe @docs recipe
@ -151,36 +126,41 @@ type alias Page pageParams pageModel pageMsg ui_pageMsg layoutModel layoutMsg ui
Internals.Page.Page pageParams pageModel pageMsg ui_pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg Internals.Page.Page pageParams pageModel pageMsg ui_pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg
{-| Turns a page and some upgrade information into a recipe, {-| Implementing the `init`, `update` and `bundle` functions is much easier
for use in a layout's `init`, `update`, and `bundle` functions! when you turn a `Page` type into `Recipe`.
import Utils.Spa as Spa A `Recipe` contains a record waiting for page specific data.
recipes : Recipes msg - `init`: just needs a `route`
recipes =
{ top =
Spa.recipe
{ page = Top.page
, toModel = TopModel
, toMsg = TopMsg
}
, counter =
Spa.recipe
{ page = Counter.page
, toModel = CounterModel
, toMsg = CounterMsg
}
-- ... - `upgrade` : just needs a `msg` and `model`
}
- `bundle` (`view`/`subscriptions`) : just needs a `model`
import Utils.Spa as Spa
recipes : Recipes msg
recipes =
{ top =
Spa.recipe
{ page = Top.page
, toModel = TopModel
, toMsg = TopMsg
}
, counter =
Spa.recipe
{ page = Counter.page
, toModel = CounterModel
, toMsg = CounterMsg
}
-- ...
}
-} -}
recipe : recipe :
{ page : Page pageParams pageModel pageMsg ui_pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg ((pageMsg -> layoutMsg) -> ui_pageMsg -> ui_layoutMsg)
, toModel : pageModel -> layoutModel -> Upgrade pageParams pageModel pageMsg ui_pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg
, toMsg : pageMsg -> layoutMsg
, map : (pageMsg -> layoutMsg) -> ui_pageMsg -> ui_layoutMsg
}
-> Recipe pageParams pageModel pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg -> Recipe pageParams pageModel pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg
recipe = recipe =
Internals.Page.upgrade Internals.Page.upgrade
@ -508,17 +488,10 @@ send =
-} -}
layout : layout :
{ map : (pageMsg -> msg) -> ui_pageMsg -> ui_msg ((pageMsg -> msg) -> ui_pageMsg -> ui_msg)
, view : -> Layout pageParams pageModel pageMsg ui_pageMsg globalModel globalMsg msg ui_msg
{ page : ui_msg
, global : globalModel
, toMsg : globalMsg -> msg
}
-> ui_msg
, recipe : Recipe pageParams pageModel pageMsg pageModel pageMsg ui_pageMsg globalModel globalMsg msg ui_msg
}
-> Page pageParams pageModel pageMsg ui_pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg -> Page pageParams pageModel pageMsg ui_pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg
layout options = layout map options =
Page Page
(\{ toModel, toMsg } -> (\{ toModel, toMsg } ->
{ init = { init =
@ -539,7 +512,7 @@ layout options =
{ fromGlobalMsg = context.fromGlobalMsg { fromGlobalMsg = context.fromGlobalMsg
, fromPageMsg = toMsg >> context.fromPageMsg , fromPageMsg = toMsg >> context.fromPageMsg
, global = context.global , global = context.global
, map = options.map , map = map
} }
in in
{ title = bundle.title { title = bundle.title

View File

@ -4,6 +4,7 @@ module App.Types exposing
, Init , Init
, Update , Update
, Bundle , Bundle
, Layout, Upgrade
) )
{-| {-|
@ -48,6 +49,11 @@ doing things by hand.
@docs Bundle @docs Bundle
# layouts and recipes
@docs Layout, Upgrade
-} -}
import Internals.Page as Page import Internals.Page as Page
@ -225,3 +231,83 @@ type alias Update layoutModel layoutMsg globalModel globalMsg =
-} -}
type alias Bundle layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg = type alias Bundle layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg =
Page.Bundle layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg Page.Bundle layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg
{-|
## creating your alias
**`src/Utils/Spa.elm`**
-- if using mdgriffith/elm-ui
import App.Types
import Element exposing (Element)
type alias Bundle msg appMsg =
App.Types.Bundle msg (Element msg) Global.Model Global.Msg appMsg (Element appMsg)
-- if using elm/html
import App.Types
import Html exposing (Html)
type alias Bundle msg appMsg =
App.Types.Bundle msg (Html msg) Global.Model Global.Msg appMsg (Html appMsg)
## using your alias
**`.elm-spa/Generated/Pages.elm`**
import Utils.Spa as Spa
bundle : Model -> Spa.Bundle Msg msg
bundle model_ =
case model_ of
-- ...
-}
type alias Layout pageParams pageModel pageMsg ui_pageMsg globalModel globalMsg msg ui_msg =
Page.Layout pageParams pageModel pageMsg ui_pageMsg globalModel globalMsg msg ui_msg
{-|
## creating your alias
**`src/Utils/Spa.elm`**
-- if using mdgriffith/elm-ui
import App.Types
import Element exposing (Element)
type alias Bundle msg appMsg =
App.Types.Bundle msg (Element msg) Global.Model Global.Msg appMsg (Element appMsg)
-- if using elm/html
import App.Types
import Html exposing (Html)
type alias Bundle msg appMsg =
App.Types.Bundle msg (Html msg) Global.Model Global.Msg appMsg (Html appMsg)
## using your alias
**`.elm-spa/Generated/Pages.elm`**
import Utils.Spa as Spa
bundle : Model -> Spa.Bundle Msg msg
bundle model_ =
case model_ of
-- ...
-}
type alias Upgrade pageParams pageModel pageMsg ui_pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg =
Page.Upgrade pageParams pageModel pageMsg ui_pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg

View File

@ -1,15 +1,14 @@
module Internals.Page exposing module Internals.Page exposing
( Bundle ( Bundle
, Init , Init
, Layout
, Page(..) , Page(..)
, Recipe , Recipe
, Update , Update
, Upgrade
, upgrade , upgrade
) )
{-| Page docs
-}
type Page pageParams pageModel pageMsg ui_pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg type Page pageParams pageModel pageMsg ui_pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg
= Page (Page_ pageParams pageModel pageMsg ui_pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg) = Page (Page_ pageParams pageModel pageMsg ui_pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg)
@ -23,8 +22,6 @@ type alias Page_ pageParams pageModel pageMsg ui_pageMsg layoutModel layoutMsg u
-> Recipe pageParams pageModel pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg -> Recipe pageParams pageModel pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg
{-| Recipe docs
-}
type alias Recipe pageParams pageModel pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg = type alias Recipe pageParams pageModel pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg =
{ init : pageParams -> Init layoutModel layoutMsg globalModel globalMsg { init : pageParams -> Init layoutModel layoutMsg globalModel globalMsg
, update : pageMsg -> pageModel -> Update layoutModel layoutMsg globalModel globalMsg , update : pageMsg -> pageModel -> Update layoutModel layoutMsg globalModel globalMsg
@ -32,14 +29,18 @@ type alias Recipe pageParams pageModel pageMsg layoutModel layoutMsg ui_layoutMs
} }
upgrade : type alias Upgrade pageParams pageModel pageMsg ui_pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg =
{ page : Page pageParams pageModel pageMsg ui_pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg { page : Page pageParams pageModel pageMsg ui_pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg
, toModel : pageModel -> layoutModel , toModel : pageModel -> layoutModel
, toMsg : pageMsg -> layoutMsg , toMsg : pageMsg -> layoutMsg
, map : (pageMsg -> layoutMsg) -> ui_pageMsg -> ui_layoutMsg
} }
upgrade :
((pageMsg -> layoutMsg) -> ui_pageMsg -> ui_layoutMsg)
-> Upgrade pageParams pageModel pageMsg ui_pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg
-> Recipe pageParams pageModel pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg -> Recipe pageParams pageModel pageMsg layoutModel layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg
upgrade config = upgrade map config =
let let
(Page page) = (Page page) =
config.page config.page
@ -47,26 +48,20 @@ upgrade config =
page page
{ toModel = config.toModel { toModel = config.toModel
, toMsg = config.toMsg , toMsg = config.toMsg
, map = config.map , map = map
} }
{-| Init docs
-}
type alias Init layoutModel layoutMsg globalModel globalMsg = type alias Init layoutModel layoutMsg globalModel globalMsg =
{ global : globalModel } { global : globalModel }
-> ( layoutModel, Cmd layoutMsg, Cmd globalMsg ) -> ( layoutModel, Cmd layoutMsg, Cmd globalMsg )
{-| Update docs
-}
type alias Update layoutModel layoutMsg globalModel globalMsg = type alias Update layoutModel layoutMsg globalModel globalMsg =
{ global : globalModel } { global : globalModel }
-> ( layoutModel, Cmd layoutMsg, Cmd globalMsg ) -> ( layoutModel, Cmd layoutMsg, Cmd globalMsg )
{-| Bundle docs
-}
type alias Bundle layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg = type alias Bundle layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg =
{ global : globalModel { global : globalModel
, fromGlobalMsg : globalMsg -> msg , fromGlobalMsg : globalMsg -> msg
@ -78,3 +73,14 @@ type alias Bundle layoutMsg ui_layoutMsg globalModel globalMsg msg ui_msg =
, view : ui_msg , view : ui_msg
, subscriptions : Sub msg , subscriptions : Sub msg
} }
type alias Layout pageParams pageModel pageMsg ui_pageMsg globalModel globalMsg msg ui_msg =
{ view :
{ page : ui_msg
, global : globalModel
, toMsg : globalMsg -> msg
}
-> ui_msg
, recipe : Recipe pageParams pageModel pageMsg pageModel pageMsg ui_pageMsg globalModel globalMsg msg ui_msg
}