project-template 0.1.4.2 → 0.2.0
raw patch · 5 files changed
+216/−18 lines, 5 filesdep +directorydep +filepathdep −system-fileiodep −system-filepath
Dependencies added: directory, filepath
Dependencies removed: system-fileio, system-filepath
Files
- ChangeLog.md +3/−0
- README.md +201/−0
- Text/ProjectTemplate.hs +7/−10
- project-template.cabal +4/−4
- test/Text/ProjectTemplateSpec.hs +1/−4
+ ChangeLog.md view
@@ -0,0 +1,3 @@+## 0.2.0++* Drop system-filepath
+ README.md view
@@ -0,0 +1,201 @@+Copy of the announcment blog post:++* * *++In my [last blog+post](http://www.yesodweb.com/blog/2012/09/building-haskell-ide), I discussed+one aspect of my work with FP Complete: the goal of creating a Haskell IDE.+Since then, I've gotten lots of incredible feedback from the community, and in+particular have been in email discussion with some of the major players in the+Haskell IDE scene. I think it's safe to say that we all agree that there's+going to be a large amount of overlap in our efforts, and we will be+coordinating to try and minimize duplicated work as much as possible, while+still providing for the unique goals of each IDE project.++In response to all this, I've created a [Wiki on+Github](https://github.com/fpco/haskell-ide/wiki) to keep track of our goals,+and a [Haskell IDE Google Group](https://groups.google.com/d/forum/haskell-ide)+for discussion. I strongly recommend joining if you're interested in any more+sophisticated Haskell code editing tools.++We have a lot of topics to cover, and a single blog post won't be nearly enough+to even scratch the surface. For now, I'd like to focus on one specific+feature. I haven't chosen to start here because it's the most important+feature, but because I think it's a problem that we can solve relatively easily+and thoroughly.++## Project Templates++Most (all?) IDEs provide the concept of a project template: instead of writing+all of the code for a project from scratch, you select a template, answer a few+questions, and a bunch of files are automatically generated. We already have+this in the Haskell world: Yesod provides the project scaffolded (via the+`yesod init` command), and I believe Snap provides something like this as well.+But these are just two examples. I'm sure we could easily come up with a dozen+other possible templates: a GTK+ application, a web services client, or a+console app.++Currently, there's no standard for how this should work in the Haskell world+or, to my knowledge, in the non-Haskell world either. (If there is, please let+me know, I'd like to be able to build on existing work.) The Yesod scaffolding (and+Snap's I believe) are both generated via specialized command line tools. I'm+sure each IDE would be fully capable of building wrappers for for two tools,+but that quickly becomes an existential complexity issue. It also makes it much+more difficult for someone to start providing a new scaffolding. I know we+suffer from this already in the Yesod world, where innovation is definitely+stifled by having the One True Blessed Scaffolding.++So here are my goals for the ideal templating system:++* A single file to represent a template. This can be some kind of archive (ZIP+ file, tarball, etc), I don't really care, but single file systems simplify+ things greatly.++* Provide a Haskell library for both generating and consuming these+ templates. We can have a command line tool as a wrapper around the library,+ but the library should be the primary means of interacting. (You'll see this as+ a pattern as I talk more about the IDE world.)++* Build on top of commonly used formats as much as possible. The reasoning+ here is that, even though we'll be providing a canonical Haskell library,+ not all IDEs are written in Haskell (yet). If someone is writing an IDE in+ Python and wants to provide Haskell support, we should make it as easy as+ possible.++ * By the way, it's worth pointing out that, as described, there's nothing+ Haskell-centric about my proposal here. I've been going in the+ direction of creating language-agnostic tools and formats as much as possible+ (e.g., [`keter`](http://hackage.haskell.org/package/keter), which can host web+ apps written in *any* language).++* I'm guessing that the most common way that people will want to actually+ provide a template is as a Git(hub)/Darcs repo. It would be great if we+ could provide a web service that takes a repo and automatically generates a+ template file. Then users of an IDE could theoretically just type in a repo URL+ to some text box and automatically get the most recent code available.++* Similarly, we should provide a simple command-line tool that takes a folder+ and generates a template file.++## A semi-concrete proposal++As many of you know, I normally prefer to discuss actual working code/ideas+than to discuss theoretical ideas. In this case, however, I think it's worth+fleshing out the idea a bit before jumping in and implementing something. So+I'm going to lay out my proposal here, and ask for everyone's input and+recommendations before we start implementation. I recommend the discussion be+targetted at the [Haskell IDE Google+Group](https://groups.google.com/d/forum/haskell-ide) as much as possible.++For file format: let's use JSON. I'm not worried about file size: these project+template files will likely be transferred over HTTP most of the time, and+compression can be performed at that level. As for binary files, we'll+base64-encode the contents.++The JSON file needs to have three sections:++1. Metadata describing the project template itself. This would be the name of+ the template, a description, author, homepage, and maybe a version.+ (Version could be automatically generated as the date it was created.) This is+ all pretty boring.++2. Data that needs to be collected from the user. In the Yesod scaffolding, we+ ask for the user's name, the project name, and the database backend to use.+ The first two are (mostly) free-form text, while the third is an enumeration. I+ think we'll need to support a few basic datatypes:++ * Text, with a regex for validation.+ * Booleans+ * Enumerations++ We can also allow default values. So to model the Yesod scaffolding,+ perhaps something like this:++ ~~~json+ {"user-fields":+ [ {"name":"user-name","type":"text","validation":".+","description":"Your name"}+ , {"name":"project-name","type":"text","validation":"[\w_]+","description":"Name of your project"}+ , {"name":"database-backend","type":"enumerator","choices":+ [{"display":"MySQL","value":"mysql"},{"display":"MongoDB","value":"mongodb"}],+ "description":"Name of your project"}+ ]}+ ~~~++3. The files that will be generated. We need to take into account some issues:++ 1. Some files will be generated conditionally based on the input from the user.+ 2. Some of the files will be named based on the user input (e.g., the name of the `cabal` file).+ 3. The actual contents of the file will depend on the user input (e.g., the *contents* of the `cabal` file).+ 4. We want to support both textual and binary files. Binary files need not have any conditional aspect to them.++ For the first issue, we'll need to have a basic expression language. I+ think equality, inequality, and, or, parantheses and variables should be+ sufficient. So to say that the file `config/postgres.yml` should only be+ generated if the database backend is postgresql, we could have something like:++ ~~~json+ {"filename":"config/postgres.yml",+ "contents":"We'll discuss this in a moment...",+ "condition":"database-backend == 'postgresql'"+ }+ ~~~++ For the conditional file naming, how about something like this:++ ~~~json+ {"filename":[{"variable":"project-name"},{"content":".cabal"}],+ "contents":"..."+ }+ ~~~++ In order to solve the third point, we'll use a combination of what we've established for points 1 and 2:++ ~~~json+ {"filename":[{"variable":"project-name"},{"content":".cabal"}],+ "contents":+ [ {"content":"name: "},+ {"variable":"project-name"},+ {"content":"...build-depends:..."},+ {"content":"\n , postgresql-simple >= 0.3 && < 0.4","condition":"database-backend == 'postgresql'"}+ ]+ }+ ~~~++ The last one is easiest to solve: each file can have a field `encoding` which is either "text" or "base64".++ ~~~json+ {"filename":"some-image.png",+ "contents":"DEADBEEF",+ "encoding":"base64"+ }+ ~~~++Once we have the file format figured out, the library is relatively simple. Let's describe a simple consumption API:++~~~haskell+data CodeTemplate+instance FromJSON CodeTemplate++data UserInputType = UIText (Maybe Regex) | UIEnumeration [(Text, Text)] | UIBool+data UserInput = UserInput+ { uiType :: UserInputType+ , uiName :: Text+ , uiDescription :: Text+ }++userInputs :: CodeTemplate -> [UserInput]+generateFiles :: CodeTemplate -> Map Text Text -> Map FilePath LByteString+~~~++Setting up a generation API for dealing with completely static files should be+simple. It will be a bit more involved to deal with conditionals, but with+properly defined ADTs it shouldn't be too bad.++## Next steps++I think the most important next step is to determine what use cases my proposal+doesn't cover. The file creating code specifically doesn't allow many common+text generation techniques, like looping, as I simply see no use case for it,+but perhaps I'm mistaken. I'm also curious to hear what other ideas people have+for project templates.+
Text/ProjectTemplate.hs view
@@ -41,11 +41,8 @@ import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) import Data.Typeable (Typeable)-import Filesystem (createTree)-import Filesystem.Path.CurrentOS (FilePath, directory,- encodeString, fromText, toText,- (</>))-import Prelude hiding (FilePath)+import System.Directory (createDirectoryIfMissing)+import System.FilePath (takeDirectory, (</>)) -- | Create a template file from a stream of file/contents combinations. --@@ -57,13 +54,13 @@ case yield bs $$ CT.decode CT.utf8 =$ sinkNull of Nothing -> do yield "{-# START_FILE BASE64 "- yield $ encodeUtf8 $ either id id $ toText fp+ yield $ encodeUtf8 $ T.pack fp yield " #-}\n" yield $ B64.joinWith "\n" 76 $ B64.encode bs yield "\n" Just _ -> do yield "{-# START_FILE "- yield $ encodeUtf8 $ either id id $ toText fp+ yield $ encodeUtf8 $ T.pack fp yield " #-}\n" yield bs yield "\n"@@ -98,7 +95,7 @@ let src | isBinary = binaryLoop =$= decode64 | otherwise = textLoop True- src =$ perFile (fromText fp')+ src =$ perFile (T.unpack fp') start binaryLoop = do@@ -141,8 +138,8 @@ => FilePath -- ^ root -> FileReceiver m receiveFS root rel = do- liftIO $ createTree $ directory fp- CB.sinkFile $ encodeString fp+ liftIO $ createDirectoryIfMissing True $ takeDirectory fp+ CB.sinkFile fp where fp = root </> rel
project-template.cabal view
@@ -1,5 +1,5 @@ name: project-template-version: 0.1.4.2+version: 0.2.0 synopsis: Specify Haskell project templates and generate files description: See initial blog post for explanation: <http://www.yesodweb.com/blog/2012/09/project-templates> homepage: https://github.com/fpco/haskell-ide@@ -10,13 +10,12 @@ category: Development build-type: Simple cabal-version: >=1.8+extra-source-files: README.md ChangeLog.md library exposed-modules: Text.ProjectTemplate build-depends: base >= 4 && < 5 , base64-bytestring- , system-filepath >= 0.4- , system-fileio >= 0.3 , text >= 0.11 , bytestring >= 0.9 , transformers >= 0.2@@ -25,6 +24,8 @@ , conduit-extra , resourcet >= 0.4.3 , containers+ , filepath+ , directory ghc-options: -Wall test-suite test@@ -42,7 +43,6 @@ , text , bytestring , containers- , system-filepath , resourcet ghc-options: -Wall
test/Text/ProjectTemplateSpec.hs view
@@ -13,12 +13,9 @@ import qualified Data.ByteString.Lazy as L import qualified Data.ByteString as S import qualified Data.Map as Map-import Filesystem.Path.CurrentOS (decodeString) import Control.Arrow (second, (***)) import Control.Applicative ((<$>)) import Data.Monoid (mconcat, mappend)-import Prelude hiding (FilePath)-import Filesystem.Path (FilePath) spec :: Spec spec = do@@ -45,7 +42,7 @@ instance Arbitrary Helper where arbitrary =- Helper . Map.fromList <$> mapM (const $ (decodeString . def "foo" . filter isAlphaNum *** S.pack . def (S.unpack "bar")) <$> arbitrary) [1..10 :: Int]+ Helper . Map.fromList <$> mapM (const $ (def "foo" . filter isAlphaNum *** S.pack . def (S.unpack "bar")) <$> arbitrary) [1..10 :: Int] where def x y | null y = x