themplate (empty) → 0.1
raw patch · 10 files changed
+384/−0 lines, 10 filesdep +basedep +configuratordep +directorysetup-changed
Dependencies added: base, configurator, directory, either, errors, filepath, optparse-applicative, text, transformers
Files
- .ghci +1/−0
- .gitignore +15/−0
- .travis.yml +24/−0
- .vim.custom +31/−0
- LICENSE +30/−0
- README.md +88/−0
- Setup.hs +7/−0
- src/Main.hs +112/−0
- src/Pattern.hs +33/−0
- themplate.cabal +43/−0
+ .ghci view
@@ -0,0 +1,1 @@+:set -isrc -idist/build/autogen -optP-include -optPdist/build/autogen/cabal_macros.h
+ .gitignore view
@@ -0,0 +1,15 @@+dist+docs+wiki+TAGS+tags+wip+.DS_Store+.*.swp+.*.swo+*.o+*.hi+*~+*#+.cabal-sandbox+cabal.sandbox.config
+ .travis.yml view
@@ -0,0 +1,24 @@+# The following enables several GHC versions to be tested; often it's enough to test only against the last release in a major GHC version. Feel free to omit lines listings versions you don't need/want testing for.+env:+ - GHCVER=7.4.2 CABALVER=1.16+ - GHCVER=7.6.3 CABALVER=1.18+ - GHCVER=head CABALVER=1.18 # see section about GHC HEAD snapshots++before_install:+ - sudo add-apt-repository -y ppa:hvr/ghc+ - sudo apt-get update+ - sudo apt-get install cabal-install-$CABALVER ghc-$GHCVER happy hlint+ - export PATH=/opt/ghc/$GHCVER/bin:$PATH++install:+ - cabal-$CABALVER update+ - cabal-$CABALVER install --only-dependencies --enable-tests --enable-benchmarks -j++script:+ - travis/script.sh+ - hlint src++matrix:+ allow_failures:+ - env: GHCVER=head CABALVER=1.18+ fast_finish: true
+ .vim.custom view
@@ -0,0 +1,31 @@+" Add the following to your .vimrc to automatically load this on startup++" if filereadable(".vim.custom")+" so .vim.custom+" endif++function StripTrailingWhitespace()+ let myline=line(".")+ let mycolumn = col(".")+ silent %s/ *$//+ call cursor(myline, mycolumn)+endfunction++" enable syntax highlighting+syntax on++" search for the tags file anywhere between here and /+set tags=TAGS;/++" highlight tabs and trailing spaces+set listchars=tab:‗‗,trail:‗+set list++" f2 runs hasktags+map <F2> :exec ":!hasktags -x -c --ignore src"<CR><CR>++" strip trailing whitespace before saving+" au BufWritePre *.hs,*.markdown silent! cal StripTrailingWhitespace()++" rebuild hasktags after saving+au BufWritePost *.hs silent! :exec ":!hasktags -x -c --ignore src"
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright 2013 Benno Fünfstück++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++1. Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++3. Neither the name of the author nor the names of his contributors+ may be used to endorse or promote products derived from this software+ without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,88 @@+Themplate+=========++[](https://travis-ci.org/bennofs/themplate)++This is a very simple program for creating project templates. ++Installing Themplate+--------------------++### From hackage:++```+$ cabal install themplate+```++### From repository:++Checkout the source code:++```+$ git clone https://github.com/bennofs/themplate+$ cd themplate+```++Build in a cabal sandbox and install:++```+$ cabal sandbox init+$ cabal install --prefix=~/.cabal # Install themplate executable to ~/.cabal/bin/themplate+```++Using Themplate+---------------++To use a exisiting template with themplate, just run:++```+$ themplate init <project name> <template name>+```++This will initialize the template with the given name in a new directory with the name of the project.++To list all available templates, run:++```+$ themplate list+```++Configuration file+------------------++The configuration file is read using configurator, the syntax is described [here](http://hackage.haskell.org/package/configurator-0.2.0.2/docs/Data-Configurator.html).+The options are passed to the template, so they can use patterns to include user-specific information. An example configuration file could look like this:++```+user { + name = "Benno Fünfstück"+ email = "benno.fuenfstueck@gmail.com"+}++github {+ user = "bennofs"+}+```++This will make the options `user.name`, `user.email` and `github.user` available to templates.++Creating templates+------------------++A template is just a subdirectory in `~/.themplate`. When a new project is created with the template, the files in that directory are+copied to the project directory. The files contents and the file names in the template can contain patterns. Patterns are enclosed+in `{{` and `}}`. In a pattern, the following special forms are substituted:++- `$$name$$` will be substituted for the value of the configuration option `name`. An error is thrown if the configuration option doesn't exist.+- `??name??` will be substituted for the value of the configuration option `name`. If the configuration option doesn't exist, the pattern will+ evaluate to the empty string.++There is one special configuration option, `project.name`. This will always be set to the current project's name. `??name??` patterns are checked prior to ``$$name$$`` patterns, so the following:++```+{{homepage: http://github.com/??github.user??/$$project.name$$/}}+```++won't throw an error if `github.user` is unset, even if `project.name` was unset.++You can find an example template at https://github.com/bennofs/dotfiles/tree/master/.themplate/.
+ Setup.hs view
@@ -0,0 +1,7 @@+{-# OPTIONS_GHC -Wall #-}+module Main (main) where++import Distribution.Simple ( defaultMain )++main :: IO ()+main = defaultMain
+ src/Main.hs view
@@ -0,0 +1,112 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Main where++import Control.Error+import Control.Exception+import Control.Monad+import Control.Monad.Trans.Class+import Data.Configurator+import Data.Configurator.Types(Config())+import Data.Monoid+import qualified Data.Text as T+import qualified Data.Text.IO as T+import Options.Applicative+import Pattern+import Prelude hiding (lookup)+import System.Directory+import System.Exit+import System.FilePath++-- | This data type represents the result of parsing the command line options.+data Cmd = Init String String Bool+ | List++-- | Command line argument parser for the 'init' subcommand. The init command can be used to instantiate templates.+initCmd :: ParserInfo Cmd+initCmd = info (helper <*> parser) (fullDesc <> progDesc "Use an installed template.")+ where nameOpt = argument Just (metavar "NAME" <> help "The project name")+ templatesOpt = argument Just (metavar "TEMPLATE" <> help "Name of the template which to use for this project")+ amendOpt = switch (short 'a' <> long "amend" <> help "Don't fail when a file exists. Instead, ignore it and continue.")+ parser = Init <$> nameOpt <*> templatesOpt <*> amendOpt++-- | Command line argument parser for the 'list' subcommand. The list subcommand prints all installed templates.+listCmd :: ParserInfo Cmd+listCmd = info (helper <*> pure List) (briefDesc <> progDesc "List all installed templates")++-- | Top level command line argument parser. Aggregates all subcommands.+themplateCmds :: Parser Cmd+themplateCmds = subparser $ mconcat+ [ command "init" initCmd+ , command "list" listCmd+ ]++-- | The parser info for this program. Specifies help and other information.+optsInfo :: ParserInfo Cmd+optsInfo = info (helper <*> themplateCmds) $ mconcat+ [ fullDesc+ , progDesc "Use project patterns to quickly create a new project"+ ]++-- | Get a list of all available templates in the given directory. The list of available templates+-- is just the list of all directories under the given path.+availableTemplates :: String -> IO [String]+availableTemplates appData = getDirectoryContents appData >>= filterM f+ where f x+ | x `elem` [".",".."] = return False+ | otherwise = doesDirectoryExist $ appData </> x++-- | Instantiate a template, copying the files and resolving all conditionals and variables.+-- The first argument is the project name, the second argument the directory of the template and the third argument is+-- the target directory, to which the files get copied.+instantiateTemplate :: String -> Bool -> Config -> FilePath -> FilePath -> EitherT T.Text IO ()+instantiateTemplate proj amend c temp target = do+ contents <- lift $ filter (not . flip elem [".",".."]) <$> getDirectoryContents temp+ dirs <- lift $ filterM (doesDirectoryExist . (temp </>)) contents+ files <- lift $ filterM (doesFileExist . (temp </>)) contents+ + forM_ files $ \file -> do+ let lookupVar "project.name" = return $ Just $ T.pack proj+ lookupVar x = lookup c x + let substitute = fmapLT (mappend $ T.pack file <> ":Error: Undeclared variable ") . substituteBetween "{{" "}}" (evalPattern lookupVar)++ content <- lift $ T.readFile (temp </> file)+ content' <- substitute content+ file' <- fmap T.unpack $ substitute $ T.pack file++ e <- lift $ doesFileExist (target </> file')+ when (e && not amend) $ left $ ":Error: Target file " <> T.pack (target </> file') <> " does already exist."+ unless e $ lift $ T.writeFile (target </> file') content'+ + forM_ dirs $ \dir -> do + lift $ createDirectoryIfMissing True $ target </> dir + instantiateTemplate proj amend c (temp </> dir) (target </> dir)++-- | Top level subcommand handler. Switches on the subcommand and performs the given action.+-- Takes 3 arguments: The configuration, the path to the appUserDataDirectory and the command.+handleCmd :: Config -> String -> Cmd -> IO ()+handleCmd _ appData List = mapM_ putStrLn =<< availableTemplates appData+handleCmd c appData (Init proj temp amend) = do+ e <- doesDirectoryExist (appData </> temp)+ unless e $ do+ putStrLn $ "Error: Template " <> temp <> " is not installed."+ exitFailure+ targetExists <- doesDirectoryExist proj+ unless targetExists $ createDirectory proj+ projPath <- canonicalizePath proj+ res <- runEitherT (instantiateTemplate proj amend c (appData </> temp) projPath) `catch` \(exc :: SomeException) ->+ unless targetExists (removeDirectoryRecursive projPath) >> throw exc+ case res of+ Left m -> do+ T.putStrLn m+ removeDirectoryRecursive projPath+ exitFailure+ Right () -> exitSuccess++main :: IO ()+main = do+ cmd <- execParser optsInfo+ appData <- getAppUserDataDirectory "themplate"+ createDirectoryIfMissing True appData+ config <- load [Required $ appData </> "config.cfg"]+ handleCmd config appData cmd
+ src/Pattern.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE OverloadedStrings #-}+module Pattern where++import Control.Applicative+import Control.Monad.Trans.Class+import Control.Monad.Trans.Either+import Control.Monad.Trans.Maybe+import Data.Maybe+import Data.Monoid+import qualified Data.Text as T++-- | Apply a function to replace the text between a given start and end tag.+-- Example:+--+-- >>> runIdentity $ substituteBetween "%" "%" (return . map toUpper) "abcd%def%gh"+-- "abcdDEFgh"+substituteBetween :: Applicative f => T.Text -> T.Text -> (T.Text -> f T.Text) -> T.Text -> f T.Text+substituteBetween open close f t+ | T.null t = pure t+ | T.null pat = (b <>) <$> substituteBetween open close f e+ | otherwise = (\x y -> b <> x <> y) <$> f pat <*> substituteBetween open close f e+ where (b,r) = T.breakOn open t+ (pat,e') = T.breakOn close $ T.drop (T.length open) r+ e = T.drop (T.length close) e'++-- | Evaluate a pattern using the lookup function @f@. A pattern may contain the following special expressions:+--+-- - $$a$$ will be replaced with @f a@, returning Left a if @f@ returns Nothing+-- - ??a?? will be replaced with @f a@, returning an empty string if @f@ returns Nothing+evalPattern :: Monad f => (T.Text -> f (Maybe T.Text)) -> T.Text -> EitherT T.Text f T.Text+evalPattern f t = replaceRequiredVar . fromMaybe T.empty =<< runMaybeT (replaceOptionalVar t)+ where replaceRequiredVar = substituteBetween "$$" "$$" (\x -> maybe (left x) return =<< lift (f x))+ replaceOptionalVar = substituteBetween "??" "??" (MaybeT . lift . f)
+ themplate.cabal view
@@ -0,0 +1,43 @@+name: themplate+version: 0.1+license: BSD3+cabal-version: >= 1.10+license-file: LICENSE+author: Benno Fünfstück+maintainer: Benno Fünfstück <benno.fuenfstueck@gmail.com>+stability: experimental+homepage: http://github.com/bennofs/themplate/+bug-reports: http://github.com/bennofs/themplate/issues+copyright: Copyright (C) 2013 Benno Fünfstück+synopsis: themplate+description: themplate+build-type: Simple+category: Application, Console, Development, Template++extra-source-files:+ .ghci+ .gitignore+ .travis.yml+ .vim.custom+ README.md++source-repository head+ type: git+ location: https://github.com/bennofs/themplate.git++executable themplate+ hs-source-dirs: src+ main-is: Main.hs+ other-modules: Pattern+ default-language: Haskell2010+ ghc-options: -Wall+ build-depends:+ base >= 4.4 && < 5+ , filepath+ , directory+ , text+ , transformers+ , either+ , optparse-applicative+ , configurator+ , errors