├── Setup.hs ├── test ├── Spec.hs └── Doctest.hs ├── templates └── sample.txt ├── CONTRIBUTING.md ├── .gitignore ├── .github ├── FUNDING.yml └── workflows │ └── test.yaml ├── stack-lts-lower.yaml.lock ├── CHANGELOG.md ├── src └── Text │ ├── Heterocephalus │ ├── Parse │ │ ├── Option.hs │ │ ├── Doc.hs │ │ └── Control.hs │ └── Parse.hs │ ├── Hamlet │ └── Parse.hs │ └── Heterocephalus.hs ├── stack-lts-10.yaml ├── stack.yaml ├── LICENSE ├── stack-lts-lower.yaml ├── heterocephalus.cabal └── README.md /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /test/Spec.hs: -------------------------------------------------------------------------------- 1 | main :: IO () 2 | main = putStrLn "Test suite not yet implemented" 3 | -------------------------------------------------------------------------------- /templates/sample.txt: -------------------------------------------------------------------------------- 1 | sample 2 | %{ forall a <- as } 3 | key: #{a}, 4 | %{ endforall } 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | How to Contribute to this Repository? 2 | ===================================== 3 | 4 | Feel free to open an issue or a pull-request. 5 | -------------------------------------------------------------------------------- /test/Doctest.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Data.Monoid ((<>)) 4 | import System.FilePath.Glob (glob) 5 | import Test.DocTest (doctest) 6 | 7 | main :: IO () 8 | -- main = glob "src/**/*.hs" >>= doDocTest 9 | main = return () 10 | 11 | doDocTest :: [String] -> IO () 12 | doDocTest options = doctest $ options <> ghcExtensions 13 | 14 | ghcExtensions :: [String] 15 | ghcExtensions = 16 | [ "-XTemplateHaskell" 17 | , "-XQuasiQuotes" 18 | ] 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | stack.yaml.lock 2 | stack-lts-10.yaml.lock 3 | ### https://raw.github.com/github/gitignore/2664dad61f3fda7755acc70ceb5aac4b1e956981/Global/Vim.gitignore 4 | 5 | # swap 6 | [._]*.s[a-w][a-z] 7 | [._]s[a-w][a-z] 8 | # session 9 | Session.vim 10 | # temporary 11 | .netrwhist 12 | *~ 13 | # auto-generated tag files 14 | tags 15 | 16 | 17 | ### https://raw.github.com/github/gitignore/2664dad61f3fda7755acc70ceb5aac4b1e956981/Haskell.gitignore 18 | 19 | dist 20 | dist-* 21 | cabal-dev 22 | *.o 23 | *.hi 24 | *.chi 25 | *.chs.h 26 | *.dyn_o 27 | *.dyn_hi 28 | .hpc 29 | .hsenv 30 | .cabal-sandbox/ 31 | cabal.sandbox.config 32 | *.prof 33 | *.aux 34 | *.hp 35 | *.eventlog 36 | .stack-work/ 37 | cabal.project.local 38 | 39 | 40 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [arowM] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /stack-lts-lower.yaml.lock: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by Stack. 2 | # You should not edit this file by hand. 3 | # For more information, please see the documentation at: 4 | # https://docs.haskellstack.org/en/stable/lock_files 5 | 6 | packages: 7 | - completed: 8 | hackage: template-haskell-compat-v0208-0.1.9@sha256:abba2d89c500b0ad6259f42be07c1a59d331970624de7e0600ba62738e2c0115,1523 9 | pantry-tree: 10 | size: 295 11 | sha256: 6ccef12b43fd6561c5e74f618e4eb9200c44966127144a5056d42d1d1378118b 12 | original: 13 | hackage: template-haskell-compat-v0208-0.1.9@sha256:abba2d89c500b0ad6259f42be07c1a59d331970624de7e0600ba62738e2c0115,1523 14 | snapshots: 15 | - completed: 16 | size: 590100 17 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/28.yaml 18 | sha256: 428ec8d5ce932190d3cbe266b9eb3c175cd81e984babf876b64019e2cbe4ea68 19 | original: lts-18.28 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | Version 1.0.5.7 (2022-07-13) 5 | ---------------- 6 | 7 | Support GHC 9.2 8 | 9 | Version 1.0.5.0 (2017-06-05) 10 | ---------------- 11 | 12 | ### New features 13 | 14 | * Add settings to be able to change the character used to deliminate control statements #18 15 | 16 | ### Document updates 17 | 18 | * Fixed small spelling/grammars on readme #19 19 | 20 | Version 1.0.4.0 (2017-02-07) 21 | ---------------- 22 | 23 | ### New features 24 | 25 | * Expose htmlSetting and textSetting 26 | 27 | Version 1.0.3.0 (2017-01-24) 28 | ---------------- 29 | 30 | ### New features 31 | 32 | * Add `case` control statement 33 | 34 | Version 1.0.2.0 (2016-12-13) 35 | ---------------------------- 36 | 37 | #### New features 38 | 39 | * Add `compileTextFileWith` and `compileHtmlFileWith` to inject extra variables 40 | * Add `ScopeM` type for specifying extra template variables 41 | * Add `setDefault` and `overwrite` for constructing `ScopeM` 42 | -------------------------------------------------------------------------------- /src/Text/Heterocephalus/Parse/Option.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE FlexibleContexts #-} 3 | 4 | module Text.Heterocephalus.Parse.Option where 5 | 6 | import Control.Monad.Reader (MonadReader, reader) 7 | 8 | data ParseOptions = ParseOptions 9 | { parseOptionsControlPrefix :: Char 10 | , parseOptionsVariablePrefix :: Char 11 | } 12 | 13 | -- | Default set of parser options. 14 | -- 15 | -- Sets 'parseOptionsControlPrefix' to @\'%\'@ and 16 | -- 'parseOptionsVariablePrefix' to @\'#\'@. 17 | defaultParseOptions :: ParseOptions 18 | defaultParseOptions = createParseOptions '%' '#' 19 | 20 | createParseOptions 21 | :: Char -- ^ The control prefix. 22 | -> Char -- ^ The variable prefix. 23 | -> ParseOptions 24 | createParseOptions controlPrefix varPrefix = ParseOptions 25 | { parseOptionsControlPrefix = controlPrefix 26 | , parseOptionsVariablePrefix = varPrefix 27 | } 28 | 29 | getControlPrefix :: MonadReader ParseOptions m => m Char 30 | getControlPrefix = reader parseOptionsControlPrefix 31 | 32 | getVariablePrefix :: MonadReader ParseOptions m => m Char 33 | getVariablePrefix = reader parseOptionsVariablePrefix 34 | -------------------------------------------------------------------------------- /src/Text/Heterocephalus/Parse.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE DeriveDataTypeable #-} 3 | {-# LANGUAGE FlexibleContexts #-} 4 | {-# LANGUAGE FlexibleInstances #-} 5 | {-# LANGUAGE LambdaCase #-} 6 | {-# LANGUAGE TemplateHaskell #-} 7 | 8 | module Text.Heterocephalus.Parse 9 | ( module Text.Heterocephalus.Parse 10 | , module Text.Heterocephalus.Parse.Control 11 | , module Text.Heterocephalus.Parse.Doc 12 | , module Text.Heterocephalus.Parse.Option 13 | ) where 14 | 15 | import Text.Heterocephalus.Parse.Control (Content(..), parseLineControl) 16 | import Text.Heterocephalus.Parse.Doc 17 | (Doc(..), parseDocFromControls) 18 | import Text.Heterocephalus.Parse.Option 19 | (ParseOptions(..), createParseOptions, defaultParseOptions) 20 | 21 | docFromString :: ParseOptions -> String -> [Doc] 22 | docFromString opts s = 23 | case parseDoc opts s of 24 | Left s' -> error s' 25 | Right d -> d 26 | 27 | parseDoc :: ParseOptions -> String -> Either String [Doc] 28 | parseDoc opts s = do 29 | controls <- parseLineControl opts s 30 | case parseDocFromControls controls of 31 | Left parseError -> Left $ show parseError 32 | Right docs -> Right docs 33 | -------------------------------------------------------------------------------- /src/Text/Hamlet/Parse.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveDataTypeable #-} 2 | {-# LANGUAGE FlexibleContexts #-} 3 | {-# LANGUAGE TemplateHaskell #-} 4 | {-# LANGUAGE FlexibleInstances #-} 5 | module Text.Hamlet.Parse 6 | ( Binding (..) 7 | , specialOrIdent 8 | , DataConstr (..) 9 | , Module (..) 10 | ) 11 | where 12 | 13 | import Data.Data (Data) 14 | import Data.Typeable (Typeable) 15 | import Text.Shakespeare.Base (Ident(..)) 16 | 17 | data Binding = BindVar Ident 18 | | BindAs Ident Binding 19 | | BindConstr DataConstr [Binding] 20 | | BindTuple [Binding] 21 | | BindList [Binding] 22 | | BindRecord DataConstr [(Ident, Binding)] Bool 23 | deriving (Eq, Show, Read, Data, Typeable) 24 | 25 | data DataConstr = DCQualified Module Ident 26 | | DCUnqualified Ident 27 | deriving (Eq, Show, Read, Data, Typeable) 28 | 29 | newtype Module = Module [String] 30 | deriving (Eq, Show, Read, Data, Typeable) 31 | 32 | -- | This funny hack is to allow us to refer to the 'or' function without 33 | -- requiring the user to have it in scope. See how this function is used in 34 | -- Text.Hamlet. 35 | specialOrIdent :: Ident 36 | specialOrIdent = Ident "__or__hamlet__special" 37 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | - pull_request 4 | jobs: 5 | test: 6 | runs-on: ${{ matrix.os }} 7 | strategy: 8 | matrix: 9 | ghc: ['8.10.7', '9.0.2', '9.2.2', 'latest'] 10 | os: [ubuntu-latest, macOS-latest, windows-latest] 11 | cabal: ['3.4', '3.6', 'latest'] 12 | exclude: 13 | - ghc: 9.2.2 14 | cabal: 3.4 15 | - ghc: latest 16 | cabal: 3.4 17 | name: Haskell GHC ${{ matrix.ghc }} sample 18 | steps: 19 | - name: Cache Cabal build artifacts 20 | uses: actions/cache@v2 21 | with: 22 | path: | 23 | ${{ steps.setup-haskell-build-env.outputs.cabal-store }} 24 | dist-newstyle 25 | key: ${{ runner.os }}-cabal-${{ matrix.ghc }}-${{ hashFiles('cabal.project.freeze') }} 26 | restore-keys: | 27 | ${{ runner.os }}-cabal-${{ matrix.ghc }} 28 | - uses: actions/checkout@v3 29 | - name: Setup Haskell 30 | uses: haskell/actions/setup@v2 31 | with: 32 | ghc-version: ${{ matrix.ghc }} 33 | cabal-version: ${{ matrix.cabal }} 34 | - name: Build & test 35 | run: | 36 | cabal v2-build 37 | cabal v2-test --enable-test 38 | -------------------------------------------------------------------------------- /stack-lts-10.yaml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by 'stack init' 2 | # 3 | # Some commonly used options have been documented as comments in this file. 4 | # For advanced use and comprehensive documentation of the format, please see: 5 | # http://docs.haskellstack.org/en/stable/yaml_configuration/ 6 | 7 | # Resolver to choose a 'specific' stackage snapshot or a compiler version. 8 | # A snapshot resolver dictates the compiler version and the set of packages 9 | # to be used for project dependencies. For example: 10 | # 11 | # resolver: lts-3.5 12 | # resolver: nightly-2015-09-21 13 | # resolver: ghc-7.10.2 14 | # resolver: ghcjs-0.1.0_ghc-7.10.2 15 | # resolver: 16 | # name: custom-snapshot 17 | # location: "./custom-snapshot.yaml" 18 | resolver: lts-10.10 19 | 20 | # User packages to be built. 21 | # Various formats can be used as shown in the example below. 22 | # 23 | # packages: 24 | # - some-directory 25 | # - https://example.com/foo/bar/baz-0.0.2.tar.gz 26 | # - location: 27 | # git: https://github.com/commercialhaskell/stack.git 28 | # commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a 29 | # - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a 30 | # extra-dep: true 31 | # subdirs: 32 | # - auto-update 33 | # - wai 34 | # 35 | # A package marked 'extra-dep: true' will only be built if demanded by a 36 | # non-dependency (i.e. a user package), and its test suites and benchmarks 37 | # will not be run. This is useful for tweaking upstream packages. 38 | packages: 39 | - '.' 40 | # Dependency packages to be pulled from upstream that are not in the resolver 41 | # (e.g., acme-missiles-0.3) 42 | extra-deps: [] 43 | 44 | # Override default flag values for local packages and extra-deps 45 | flags: {} 46 | 47 | # Extra package databases containing global packages 48 | extra-package-dbs: [] 49 | 50 | # Control whether we use the GHC we find on the path 51 | # system-ghc: true 52 | # 53 | # Require a specific version of stack, using version ranges 54 | # require-stack-version: -any # Default 55 | # require-stack-version: ">=1.2" 56 | # 57 | # Override the architecture used by stack, especially useful on Windows 58 | # arch: i386 59 | # arch: x86_64 60 | # 61 | # Extra directories used by stack for building 62 | # extra-include-dirs: [/path/to/dir] 63 | # extra-lib-dirs: [/path/to/dir] 64 | # 65 | # Allow a newer minor version of GHC than the snapshot specifies 66 | # compiler-check: newer-minor 67 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by 'stack init' 2 | # 3 | # Some commonly used options have been documented as comments in this file. 4 | # For advanced use and comprehensive documentation of the format, please see: 5 | # http://docs.haskellstack.org/en/stable/yaml_configuration/ 6 | 7 | # Resolver to choose a 'specific' stackage snapshot or a compiler version. 8 | # A snapshot resolver dictates the compiler version and the set of packages 9 | # to be used for project dependencies. For example: 10 | # 11 | # resolver: lts-3.5 12 | # resolver: nightly-2015-09-21 13 | # resolver: ghc-7.10.2 14 | # resolver: ghcjs-0.1.0_ghc-7.10.2 15 | # resolver: 16 | # name: custom-snapshot 17 | # location: "./custom-snapshot.yaml" 18 | resolver: nightly-2022-07-10 19 | 20 | # User packages to be built. 21 | # Various formats can be used as shown in the example below. 22 | # 23 | # packages: 24 | # - some-directory 25 | # - https://example.com/foo/bar/baz-0.0.2.tar.gz 26 | # - location: 27 | # git: https://github.com/commercialhaskell/stack.git 28 | # commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a 29 | # - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a 30 | # extra-dep: true 31 | # subdirs: 32 | # - auto-update 33 | # - wai 34 | # 35 | # A package marked 'extra-dep: true' will only be built if demanded by a 36 | # non-dependency (i.e. a user package), and its test suites and benchmarks 37 | # will not be run. This is useful for tweaking upstream packages. 38 | packages: 39 | - '.' 40 | # Dependency packages to be pulled from upstream that are not in the resolver 41 | # (e.g., acme-missiles-0.3) 42 | extra-deps: [] 43 | 44 | # Override default flag values for local packages and extra-deps 45 | flags: {} 46 | 47 | # Extra package databases containing global packages 48 | extra-package-dbs: [] 49 | 50 | # Control whether we use the GHC we find on the path 51 | # system-ghc: true 52 | # 53 | # Require a specific version of stack, using version ranges 54 | # require-stack-version: -any # Default 55 | # require-stack-version: ">=1.2" 56 | # 57 | # Override the architecture used by stack, especially useful on Windows 58 | # arch: i386 59 | # arch: x86_64 60 | # 61 | # Extra directories used by stack for building 62 | # extra-include-dirs: [/path/to/dir] 63 | # extra-lib-dirs: [/path/to/dir] 64 | # 65 | # Allow a newer minor version of GHC than the snapshot specifies 66 | # compiler-check: newer-minor 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Kadzuya OKAMOTO, http://www.arow.info 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | This project contains many codes from Michael Snoyman's [shakespearen template](http://hackage.haskell.org/package/shakespeare). 24 | Here is the original copyright notice for shakespearen template: 25 | 26 | Copyright (c) 2012 Michael Snoyman, http://www.yesodweb.com/ 27 | 28 | Permission is hereby granted, free of charge, to any person obtaining 29 | a copy of this software and associated documentation files (the 30 | "Software"), to deal in the Software without restriction, including 31 | without limitation the rights to use, copy, modify, merge, publish, 32 | distribute, sublicense, and/or sell copies of the Software, and to 33 | permit persons to whom the Software is furnished to do so, subject to 34 | the following conditions: 35 | 36 | The above copyright notice and this permission notice shall be 37 | included in all copies or substantial portions of the Software. 38 | 39 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 40 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 41 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 42 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 43 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 44 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 45 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 46 | -------------------------------------------------------------------------------- /stack-lts-lower.yaml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by 'stack init' 2 | # 3 | # Some commonly used options have been documented as comments in this file. 4 | # For advanced use and comprehensive documentation of the format, please see: 5 | # http://docs.haskellstack.org/en/stable/yaml_configuration/ 6 | 7 | # Resolver to choose a 'specific' stackage snapshot or a compiler version. 8 | # A snapshot resolver dictates the compiler version and the set of packages 9 | # to be used for project dependencies. For example: 10 | # 11 | # resolver: lts-3.5 12 | # resolver: nightly-2015-09-21 13 | # resolver: ghc-7.10.2 14 | # resolver: ghcjs-0.1.0_ghc-7.10.2 15 | # resolver: 16 | # name: custom-snapshot 17 | # location: "./custom-snapshot.yaml" 18 | resolver: lts-18.28 19 | 20 | # User packages to be built. 21 | # Various formats can be used as shown in the example below. 22 | # 23 | # packages: 24 | # - some-directory 25 | # - https://example.com/foo/bar/baz-0.0.2.tar.gz 26 | # - location: 27 | # git: https://github.com/commercialhaskell/stack.git 28 | # commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a 29 | # - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a 30 | # extra-dep: true 31 | # subdirs: 32 | # - auto-update 33 | # - wai 34 | # 35 | # A package marked 'extra-dep: true' will only be built if demanded by a 36 | # non-dependency (i.e. a user package), and its test suites and benchmarks 37 | # will not be run. This is useful for tweaking upstream packages. 38 | packages: 39 | - '.' 40 | # Dependency packages to be pulled from upstream that are not in the resolver 41 | # (e.g., acme-missiles-0.3) 42 | extra-deps: 43 | - template-haskell-compat-v0208-0.1.9@sha256:abba2d89c500b0ad6259f42be07c1a59d331970624de7e0600ba62738e2c0115,1523 44 | 45 | # Override default flag values for local packages and extra-deps 46 | flags: {} 47 | 48 | # Extra package databases containing global packages 49 | extra-package-dbs: [] 50 | 51 | # Control whether we use the GHC we find on the path 52 | # system-ghc: true 53 | # 54 | # Require a specific version of stack, using version ranges 55 | # require-stack-version: -any # Default 56 | # require-stack-version: ">=1.2" 57 | # 58 | # Override the architecture used by stack, especially useful on Windows 59 | # arch: i386 60 | # arch: x86_64 61 | # 62 | # Extra directories used by stack for building 63 | # extra-include-dirs: [/path/to/dir] 64 | # extra-lib-dirs: [/path/to/dir] 65 | # 66 | # Allow a newer minor version of GHC than the snapshot specifies 67 | # compiler-check: newer-minor 68 | -------------------------------------------------------------------------------- /heterocephalus.cabal: -------------------------------------------------------------------------------- 1 | name: heterocephalus 2 | version: 1.0.5.7 3 | synopsis: A type-safe template engine for working with front end development tools 4 | description: 5 | Recent front end development tools and languages are growing fast and have 6 | quite a complicated ecosystem. Few front end developers want to be forced 7 | use Shakespeare templates. Instead, they would rather use @node@-friendly 8 | engines such that @pug@, @slim@, and @haml@. However, in using these 9 | template engines, we lose the compile-time variable interpolation and type 10 | checking from Shakespeare. 11 | . 12 | Heterocephalus is intended for use with another feature rich template 13 | engine and provides a way to interpolate server side variables into a 14 | precompiled template file with @forall@, @if@, and @case@ statements. 15 | 16 | homepage: https://github.com/arowM/heterocephalus#readme 17 | license: MIT 18 | license-file: LICENSE 19 | author: Kadzuya Okamoto 20 | maintainer: arow.okamoto+github@gmail.com 21 | copyright: 2016 Kadzuya Okamoto 22 | category: Web 23 | build-type: Simple 24 | extra-source-files: templates/*.txt 25 | README.md 26 | CHANGELOG.md 27 | cabal-version: >=1.10 28 | 29 | library 30 | hs-source-dirs: src 31 | exposed-modules: Text.Heterocephalus 32 | other-modules: Text.Hamlet.Parse 33 | , Text.Heterocephalus.Parse 34 | , Text.Heterocephalus.Parse.Control 35 | , Text.Heterocephalus.Parse.Doc 36 | , Text.Heterocephalus.Parse.Option 37 | build-depends: base >= 4.14.3.0 && < 5 38 | , blaze-html >= 0.9.1.2 39 | , blaze-markup >= 0.8.2.8 40 | , containers >= 0.6.4.1 41 | , dlist >= 1.0 42 | , mtl >= 2.2.2 43 | , parsec >= 3.1.14.0 44 | , shakespeare >= 2.0.26 45 | , template-haskell >= 2.16.0.0 46 | , template-haskell-compat-v0208 >= 0.1.7 47 | , text >= 1.2.4.1 48 | , transformers >= 0.5.6.2 49 | ghc-options: -Wall -Wcompat -Wnoncanonical-monad-instances 50 | default-language: Haskell2010 51 | 52 | test-suite heterocephalus-test 53 | type: exitcode-stdio-1.0 54 | hs-source-dirs: test 55 | main-is: Spec.hs 56 | build-depends: base 57 | , heterocephalus 58 | ghc-options: -threaded -rtsopts -with-rtsopts=-N 59 | default-language: Haskell2010 60 | 61 | test-suite doctest 62 | type: exitcode-stdio-1.0 63 | hs-source-dirs: test 64 | main-is: Doctest.hs 65 | build-depends: base 66 | , Glob 67 | , doctest >= 0.10 68 | , heterocephalus 69 | ghc-options: -threaded -rtsopts -with-rtsopts=-N 70 | default-language: Haskell2010 71 | 72 | source-repository head 73 | type: git 74 | location: https://github.com/arowM/heterocephalus 75 | -------------------------------------------------------------------------------- /src/Text/Heterocephalus/Parse/Doc.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE DeriveDataTypeable #-} 3 | {-# LANGUAGE FlexibleContexts #-} 4 | {-# LANGUAGE FlexibleInstances #-} 5 | {-# LANGUAGE LambdaCase #-} 6 | {-# LANGUAGE TemplateHaskell #-} 7 | 8 | module Text.Heterocephalus.Parse.Doc where 9 | 10 | #if MIN_VERSION_base(4,9,0) 11 | #else 12 | import Control.Applicative ((*>), (<*), pure) 13 | #endif 14 | import Control.Monad (void) 15 | import Data.Data (Data) 16 | import Data.Typeable (Typeable) 17 | import Text.Parsec 18 | (Parsec, ParseError, SourcePos, (<|>), eof, incSourceLine, many, 19 | many1, optional, optionMaybe, parse, tokenPrim) 20 | import Text.Shakespeare.Base (Deref) 21 | 22 | import Text.Hamlet.Parse 23 | import Text.Heterocephalus.Parse.Control (Content(..), Control(..)) 24 | 25 | data Doc = DocForall Deref Binding [Doc] 26 | | DocCond [(Deref, [Doc])] (Maybe [Doc]) 27 | | DocCase Deref [(Binding, [Doc])] 28 | | DocContent Content 29 | deriving (Data, Eq, Read, Show, Typeable) 30 | 31 | type DocParser = Parsec [Control] () 32 | 33 | parseDocFromControls :: [Control] -> Either ParseError [Doc] 34 | parseDocFromControls = parse (docsParser <* eof) "" 35 | 36 | docsParser :: DocParser [Doc] 37 | docsParser = many docParser 38 | 39 | docParser :: DocParser Doc 40 | docParser = forallDoc <|> condDoc <|> caseDoc <|> contentDoc 41 | 42 | forallDoc :: DocParser Doc 43 | forallDoc = do 44 | ControlForall deref binding <- forallControlStatement 45 | innerDocs <- docsParser 46 | void endforallControlStatement 47 | pure $ DocForall deref binding innerDocs 48 | 49 | condDoc :: DocParser Doc 50 | condDoc = do 51 | ControlIf ifDeref <- ifControlStatement 52 | ifInnerDocs <- docsParser 53 | elseIfs <- condElseIfs 54 | maybeElseInnerDocs <- optionMaybe $ elseControlStatement *> docsParser 55 | void endifControlStatement 56 | let allConds = (ifDeref, ifInnerDocs) : elseIfs 57 | pure $ DocCond allConds maybeElseInnerDocs 58 | 59 | caseDoc :: DocParser Doc 60 | caseDoc = do 61 | ControlCase caseDeref <- caseControlStatement 62 | -- Ignore a single, optional NoControl statement (with whitespace that will be 63 | -- ignored). 64 | optional contentDoc 65 | caseOfs <- many1 $ do 66 | ControlCaseOf caseBinding <- caseOfControlStatement 67 | innerDocs <- docsParser 68 | pure (caseBinding, innerDocs) 69 | void endcaseControlStatement 70 | pure $ DocCase caseDeref caseOfs 71 | 72 | contentDoc :: DocParser Doc 73 | contentDoc = primControlStatement $ \case 74 | NoControl content -> Just $ DocContent content 75 | _ -> Nothing 76 | 77 | condElseIfs :: DocParser [(Deref, [Doc])] 78 | condElseIfs = many $ do 79 | ControlElseIf elseIfDeref <- elseIfControlStatement 80 | elseIfInnerDocs <- docsParser 81 | pure (elseIfDeref, elseIfInnerDocs) 82 | 83 | ifControlStatement :: DocParser Control 84 | ifControlStatement = primControlStatement $ \case 85 | ControlIf deref -> Just $ ControlIf deref 86 | _ -> Nothing 87 | 88 | elseIfControlStatement :: DocParser Control 89 | elseIfControlStatement = primControlStatement $ \case 90 | ControlElseIf deref -> Just $ ControlElseIf deref 91 | _ -> Nothing 92 | 93 | elseControlStatement :: DocParser Control 94 | elseControlStatement = primControlStatement $ \case 95 | ControlElse -> Just ControlElse 96 | _ -> Nothing 97 | 98 | endifControlStatement :: DocParser Control 99 | endifControlStatement = primControlStatement $ \case 100 | ControlEndIf -> Just ControlEndIf 101 | _ -> Nothing 102 | 103 | caseControlStatement :: DocParser Control 104 | caseControlStatement = primControlStatement $ \case 105 | ControlCase deref -> Just $ ControlCase deref 106 | _ -> Nothing 107 | 108 | caseOfControlStatement :: DocParser Control 109 | caseOfControlStatement = primControlStatement $ \case 110 | ControlCaseOf binding -> Just $ ControlCaseOf binding 111 | _ -> Nothing 112 | 113 | endcaseControlStatement :: DocParser Control 114 | endcaseControlStatement = primControlStatement $ \case 115 | ControlEndCase -> Just ControlEndCase 116 | _ -> Nothing 117 | 118 | forallControlStatement :: DocParser Control 119 | forallControlStatement = primControlStatement $ \case 120 | ControlForall deref binding -> Just $ ControlForall deref binding 121 | _ -> Nothing 122 | 123 | endforallControlStatement :: DocParser Control 124 | endforallControlStatement = primControlStatement $ \case 125 | ControlEndForall -> Just ControlEndForall 126 | _ -> Nothing 127 | 128 | primControlStatement :: (Control -> Maybe x)-> DocParser x 129 | primControlStatement = tokenPrim show incSourcePos 130 | 131 | incSourcePos :: SourcePos -> a -> b -> SourcePos 132 | incSourcePos sourcePos _ _ = incSourceLine sourcePos 1 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![test](https://github.com/arowM/heterocephalus/actions/workflows/test.yaml/badge.svg)](https://github.com/arowM/heterocephalus/actions/workflows/test.yaml) 2 | [![Hackage](https://img.shields.io/hackage/v/heterocephalus.svg)](https://hackage.haskell.org/package/heterocephalus) 3 | [![Stackage LTS](http://stackage.org/package/heterocephalus/badge/lts)](http://stackage.org/lts/package/heterocephalus) 4 | [![Stackage Nightly](http://stackage.org/package/heterocephalus/badge/nightly)](http://stackage.org/nightly/package/heterocephalus) 5 | 6 | ![hetero-mini](https://cloud.githubusercontent.com/assets/1481749/20267445/2a9da33e-aabe-11e6-8aa7-88e36f0a8d5d.jpg) 7 | 8 | # Heterocephalus template engine 9 | 10 | A type-safe template engine for working with popular front end development tools. 11 | 12 | Any PRs are welcome, even for documentation fixes. (The main author of this library is not an English native.) 13 | 14 | * [Who should use this?](#who-should-use-this) 15 | * [Features](#features) 16 | * [Usage](#usage) 17 | * [Checking behaviours in `ghci`](#checking-behaviours-in-ghci) 18 | * [Syntax](#syntax) 19 | * [Why "heterocephalus"?](#why-heterocephalus) 20 | 21 | ## Who should use this? 22 | 23 | If you are planning to use Haskell with recent web front-end tools like gulp, 24 | webpack, npm, etc, then this library can help you! 25 | 26 | There are many Haskell template engines today. 27 | [Shakespeare](http://hackage.haskell.org/package/shakespeare) is great because 28 | it checks template variables at compile time. Using Shakespeare, it's not 29 | possible for the template file to cause a runtime-error. 30 | 31 | Shakespeare provides its own original ways of writing HTML 32 | ([Hamlet](https://hackage.haskell.org/package/shakespeare/docs/Text-Hamlet.html)), 33 | CSS 34 | ([Cassius](https://hackage.haskell.org/package/shakespeare/docs/Text-Cassius.html) 35 | / 36 | [Lucius](https://hackage.haskell.org/package/shakespeare/docs/Text-Lucius.html)), 37 | and JavaScript 38 | ([Julius](https://hackage.haskell.org/package/shakespeare-2.0.11.2/docs/Text-Julius.html)). 39 | If you use these original markup languages, it is possible to use control 40 | statements like `forall` (for looping), `if` (for conditionals), and `case` 41 | (for case-splitting). 42 | 43 | However, if you're using any other markup language (like 44 | [pug](https://pugjs.org), [slim](http://slim-lang.com/), 45 | [haml](http://haml.info/), normal HTML, normal CSS, etc), Shakespeare only 46 | provides you with the 47 | [Text.Shakespeare.Text](https://hackage.haskell.org/package/shakespeare/docs/Text-Shakespeare-Text.html) 48 | module. This gives you variable interpolation, but no control statements like 49 | `forall`, `if`, or `case`. 50 | 51 | [`Haiji`](https://hackage.haskell.org/package/haiji) is another interesting 52 | library. It has all the features we require, but its templates take a very 53 | [long time to compile](https://github.com/blueimpact/kucipong/pull/7) with 54 | GHC >= 7.10. 55 | 56 | Heterocephalus fills this missing niche. It gives you variable interpolation 57 | along with control statements that can be used with any markup language. Its 58 | compile times are reasonable. 59 | 60 | ## Features 61 | 62 | Here are the main features of this module. 63 | 64 | * __DO__ ensure that all interpolated variables are in scope 65 | 66 | * __DO__ ensure that all interpolated variables have proper types for the template 67 | 68 | * __DO__ expand the template literal on compile time 69 | 70 | * __DO__ provide a way to use `forall`, `if`, and `case` statments in the template 71 | 72 | `Text.Shakespeare.Text.text` has a way to do variable interpolation, but no 73 | way to use these types of control statements. 74 | 75 | * __DO NOT__ enforce that templates obey a peculiar syntax 76 | 77 | Shakespeare templates make you use their original style (Hamlet, Cassius, 78 | Lucius, Julius, etc). The 79 | [`Text.Shakespeare.Text.text`](https://hackage.haskell.org/package/shakespeare/docs/Text-Shakespeare-Text.html#v:text) 80 | function does not require you to use any particular style, but it does not 81 | have control statements like `forall`, `if` and `case`. 82 | 83 | This makes it impossible to use Shakespeare with another template engine 84 | such as `pug` in front end side. It is not suitable for recent rich front 85 | end tools. 86 | 87 | * __DO NOT__ have a long compile time 88 | 89 | `haiji` is another awesome template library. It has many of our required 90 | features, but it takes too long to compile when used with ghc >= 7.10. 91 | 92 | * __DO NOT__ provide unneeded control statements 93 | 94 | Other template engines like [EDE](https://hackage.haskell.org/package/ede) 95 | provide rich control statements like importing external files. 96 | Heterocephalus does not provide control statements like this because it is 97 | meant to be used with a rich front-end template engine (like pug, slim, 98 | etc). 99 | 100 | ## Usage 101 | 102 | You can compile external template files with the following four functions: 103 | 104 | * `compileTextFile`: A basic function that embeds variables without escaping and without default values. 105 | * `compileTextFileWithDefault`: Same as `compileTextFile` but you can set default template values. 106 | * `compileHtmlFile`: Same as `compileTextFile` but all embeded variables are escaped for html. 107 | * `compileHtmlFileWithDefault`: Same as `compileHtmlFile` but you can set default template values. 108 | 109 | For more details, see the [latest haddock 110 | document](https://www.stackage.org/haddock/nightly/heterocephalus/Text-Heterocephalus.html). 111 | 112 | ## Checking behaviours in `ghci` 113 | 114 | To check the behaviour, you can test in `ghci` as follows. Note that 115 | `compileText` and `compileHtml` are used for checking syntaxes. 116 | 117 | ```haskell 118 | $ stack install heterocephalus # Only first time 119 | $ stack repl --no-build --no-load 120 | Prelude> :m Text.Heterocephalus Text.Blaze.Renderer.String 121 | Prelude> :set -XTemplateHaskell -XQuasiQuotes 122 | Prelude> let a = 34; b = "