Skip to content
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

4039 namespaced templates #4103

Merged
merged 17 commits into from Jun 24, 2018
Merged
Changes from 14 commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.
+109 −17
Diff settings

Always

Just for now

@@ -25,6 +25,9 @@ Other enhancements:
[#4068](https://github.com/commercialhaskell/stack/pull/4068).
* Added new `--tar-dir` option to `stack sdist`, that allows to copy
the resulting tarball to the specified directory.
* `stack new` now allows template names of the form `username/foo` to download
from a user other than `commercialstack` on Github, and can be prefixed with
the service `github:`, `gitlab:`, or `bitbucket:`.

Bug fixes:

@@ -1559,6 +1559,12 @@ yesod-postgres
yesod-postgres-fay
yesod-simple
yesod-sqlite
```

You can specify one of these templates following your project name
in the `stack new` command:

```
michael@d30748af6d3d:~$ stack new my-yesod-project yesod-simple
Downloading template "yesod-simple" to create project "my-yesod-project" in my-yesod-project/ ...
Using the following authorship configuration:
@@ -1574,13 +1580,36 @@ Selected resolver: lts-3.2
Wrote project config to: /home/michael/my-yesod-project/stack.yaml
```

Alternatively you can use your own templates by specifying the path:
The default `stack-templates` repository is on [Github](https://github.com),

This comment has been minimized.

Copy link
@snoyberg

snoyberg Jun 21, 2018

Contributor

How about adding a link directly to the repo?

This comment has been minimized.

Copy link
@lylek

lylek Jun 22, 2018

Author Contributor

Sure, will do. There's also a direct link to the repo later in the section.

under the user account `commercialstack`. You can download templates from a
different Github user by prefixing the username and a slash:

```
stack new my-yesod-project yesodweb/yesod-simple

This comment has been minimized.

Copy link
@snoyberg

snoyberg Jun 21, 2018

Contributor

I think it would be worth specifying exactly how this is resolved: user yesodweb, repo stack-templates, file yesod-simple.hsfiles.

This comment has been minimized.

Copy link
@lylek

lylek Jun 22, 2018

Author Contributor

Yeah, that sounds good.

```

You can even download templates from a service other that Github, such as
[Gitlab](https://gitlab.com) or [Bitbucket](https://bitbucket.com):

```
stack new my-project gitlab:user29/foo
```

If you need more flexibility, you can specify the full URL of the template:

```
stack new my-project https://my-site.com/content/template9.hsfiles
```

(The `.hsfiles` extension is optional; it will be added if it's not specified.)

Alternatively you can use a local template by specifying the path:

```
stack new project ~/location/of/your/template.hsfiles
```

As a starting point you can use [the "simple" template](https://github.com/commercialhaskell/stack-templates/blob/master/simple.hsfiles).
As a starting point for creating your own templates, you can use [the "simple" template](https://github.com/commercialhaskell/stack-templates/blob/master/simple.hsfiles).
An introduction into template-writing and a place for submitting official templates,
you will find at [the stack-templates repository](https://github.com/commercialhaskell/stack-templates#readme).

@@ -126,10 +126,7 @@ loadTemplate name logIt = do
templateDir <- view $ configL.to templatesDir
case templatePath name of
AbsPath absFile -> logIt LocalTemp >> loadLocalFile absFile
UrlPath s -> do
req <- parseRequest s
let rel = fromMaybe backupUrlRelPath (parseRelFile s)
downloadTemplate req (templateDir </> rel)
UrlPath s -> downloadFromUrl s templateDir
RelPath relFile ->
catch
(do f <- loadLocalFile relFile
@@ -141,6 +138,10 @@ loadTemplate name logIt = do
(templateDir </> relFile)
Nothing -> throwM e
)
RepoPath rtp -> do
let url = urlFromRepoTemplatePath rtp
downloadFromUrl (T.unpack url) templateDir

where
loadLocalFile :: Path b File -> RIO env Text
loadLocalFile path = do
@@ -150,8 +151,16 @@ loadTemplate name logIt = do
if exists
then liftIO (fmap (T.decodeUtf8With T.lenientDecode) (SB.readFile (toFilePath path)))
else throwM (FailedToLoadTemplate name (toFilePath path))
relRequest :: MonadThrow n => Path Rel File -> n Request
relRequest rel = parseRequest (defaultTemplateUrl <> "/" <> toFilePath rel)
relRequest :: Path Rel File -> Maybe Request
relRequest rel = do
rtp <- parseRepoPathWithService defaultRepoService (T.pack (toFilePath rel))
let url = urlFromRepoTemplatePath rtp
parseRequest (T.unpack url)
downloadFromUrl :: String -> Path Abs Dir -> RIO env Text
downloadFromUrl s templateDir = do
req <- parseRequest s
let rel = fromMaybe backupUrlRelPath (parseRelFile s)
downloadTemplate req (templateDir </> rel)
downloadTemplate :: Request -> Path Abs File -> RIO env Text
downloadTemplate req path = do
logIt RemoteTemp
@@ -162,6 +171,15 @@ loadTemplate name logIt = do
loadLocalFile path
backupUrlRelPath = $(mkRelFile "downloaded.template.file.hsfiles")

-- | Construct a URL for downloading from a repo.
urlFromRepoTemplatePath :: RepoTemplatePath -> Text
urlFromRepoTemplatePath (RepoTemplatePath Github user name) =
T.concat ["https://raw.githubusercontent.com", "/", user, "/stack-templates/master/", name]
urlFromRepoTemplatePath (RepoTemplatePath Gitlab user name) =
T.concat ["https://gitlab.com", "/", user, "/stack-templates/raw/master/", name]
urlFromRepoTemplatePath (RepoTemplatePath Bitbucket user name) =
T.concat ["https://bitbucket.org", "/", user, "/stack-templates/raw/master/", name]

-- | Apply and unpack a template into a directory.
applyTemplate
:: HasConfig env
@@ -323,10 +341,9 @@ parseTemplateSet a = do
defaultTemplateName :: TemplateName
defaultTemplateName = $(mkTemplateName "new-template")

-- | Default web root URL to download from.
defaultTemplateUrl :: String
defaultTemplateUrl =
"https://raw.githubusercontent.com/commercialhaskell/stack-templates/master"
-- | The default service to use to download templates.
defaultRepoService :: RepoService
defaultRepoService = Github

-- | Default web URL to get a yaml file containing template metadata.
defaultTemplateInfoUrl :: String
@@ -23,9 +23,12 @@ newOptsParser = (,) <$> newOpts <*> initOptsParser
help "Do not create a subdirectory for the project") <*>
optional (templateNameArgument
(metavar "TEMPLATE_NAME" <>
help "Name of a template or a local template in a file or a URL.\
\ For example: foo or foo.hsfiles or ~/foo or\
\ https://example.com/foo.hsfiles")) <*>
help "Name of a template - can take the form\
\ [service:][username/]template with optional service name\

This comment has been minimized.

Copy link
@snoyberg

snoyberg Jun 21, 2018

Contributor

Perhaps more accurate would be:

[[service:]username/]template

This comment has been minimized.

Copy link
@lylek

lylek Jun 22, 2018

Author Contributor

No, actually you can write github:foo and it will pull the template foo from GitHub with the username commercialhaskell in the stack-templates repo. So either the service or the username can be dropped.

This comment has been minimized.

Copy link
@snoyberg

snoyberg Jun 22, 2018

Contributor

Thanks for clarifying. In that case, I think it would be better to change the behavior, otherwise we'll be granting special status to the commercialhaskell account on other services when we don't necessarily control it.

This comment has been minimized.

Copy link
@lylek

lylek Jun 22, 2018

Author Contributor

Yeah, I thought that might be odd too. I'll change the code.

\ (github, gitlab, or bitbucket) \
\ and username for the service; or, a local filename such as\
\ foo.hsfiles or ~/foo; or, a full URL such as\
\ https://example.com/foo.hsfiles.")) <*>
fmap
M.fromList
(many
@@ -30,8 +30,21 @@ data TemplatePath = AbsPath (Path Abs File)
-- the template repository
| UrlPath String
-- ^ a full URL
| RepoPath RepoTemplatePath
deriving (Eq, Ord, Show)

-- | Details for how to access a template from a remote repo.
data RepoTemplatePath = RepoTemplatePath
{ rtpService :: RepoService
, rtpUser :: Text
, rtpTemplate :: Text
}
deriving (Eq, Ord, Show)

-- | Services from which templates can be retrieved from a repository.
data RepoService = Github | Gitlab | Bitbucket
deriving (Eq, Ord, Show)

instance FromJSON TemplateName where
parseJSON = withText "TemplateName" $
either fail return . parseTemplateNameFromString . T.unpack
@@ -79,12 +92,13 @@ parseTemplateNameFromString fname =
$ asum (validParses prefix hsf orig)
validParses prefix hsf orig =
-- NOTE: order is important
[ TemplateName (T.pack orig) . UrlPath <$> (parseRequest orig *> Just orig)
[ TemplateName prefix . RepoPath <$> parseRepoPath hsf
, TemplateName (T.pack orig) . UrlPath <$> (parseRequest orig *> Just orig)
, TemplateName prefix . AbsPath <$> parseAbsFile hsf
, TemplateName prefix . RelPath <$> parseRelFile hsf
]
expected = "Expected a template like: foo or foo.hsfiles or\
\ https://example.com/foo.hsfiles"
\ https://example.com/foo.hsfiles or github:user/foo"

-- | Make a template name.
mkTemplateName :: String -> Q Exp
@@ -98,6 +112,11 @@ mkTemplateName s =
AbsPath (Path fp) -> [|AbsPath (Path fp)|]
RelPath (Path fp) -> [|RelPath (Path fp)|]
UrlPath fp -> [|UrlPath fp|]
RepoPath (RepoTemplatePath sv u t) ->
case sv of
Github -> [|RepoPath $ RepoTemplatePath Github u t|]
Gitlab -> [|RepoPath $ RepoTemplatePath Gitlab u t|]
Bitbucket -> [|RepoPath $ RepoTemplatePath Bitbucket u t|]

This comment has been minimized.

Copy link
@johnmendonca

johnmendonca Jun 23, 2018

Contributor

I was wondering about this part, since I'm not clear about what it does exactly.

Could it do without the pattern match and just be:

RepoPath (RepoTemplatePath sv u t) -> [|RepoPath $ RepoTemplatePath sv u t|]

This comment has been minimized.

Copy link
@lylek

lylek Jun 23, 2018

Author Contributor

No, that would cause TemplateHaskell to complain that there is no instance of something-or-other class. (Rep?) The alternative is to define an instance of that class, but Michael's recommendation was that it wasn't worth it.


-- | Get a text representation of the template name.
templateName :: TemplateName -> Text
@@ -106,3 +125,24 @@ templateName (TemplateName prefix _) = prefix
-- | Get the path of the template.
templatePath :: TemplateName -> TemplatePath
templatePath (TemplateName _ fp) = fp

defaultRepoUser :: Text
defaultRepoUser = "commercialhaskell"

-- | Parses a template path of the form @github:user/template@.
parseRepoPath :: String -> Maybe RepoTemplatePath
parseRepoPath s =
case T.splitOn ":" (T.pack s) of
["github" , rest] -> parseRepoPathWithService Github rest
["gitlab" , rest] -> parseRepoPathWithService Gitlab rest
["bitbucket" , rest] -> parseRepoPathWithService Bitbucket rest
_ -> Nothing

-- | Parses a template path of the form @user/template@, assuming the default service.
parseRepoPathWithService :: RepoService -> Text -> Maybe RepoTemplatePath
parseRepoPathWithService service path =
case T.splitOn "/" path of
[user, name] -> Just $ RepoTemplatePath service user name
[name] -> Just $ RepoTemplatePath service defaultRepoUser name

This comment has been minimized.

Copy link
@johnmendonca

johnmendonca Jun 23, 2018

Contributor

Deleting this line and defaultRepoUser should change the behavior as described above regarding commercialhaskell on other services.

This comment has been minimized.

Copy link
@lylek

lylek Jun 23, 2018

Author Contributor

Not quite. That would prevent commercialhaskell being used as the default for other services. But it would also break the simple case where only a template name is supplied. Another function will have to be created to handle that case.

This comment has been minimized.

Copy link
@johnmendonca

johnmendonca Jun 23, 2018

Contributor

Oh right, we end up here in the case where just a single name is given, thanks.

_ -> Nothing

ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.