packages feed

tintin (empty) → 1.0.3

raw patch · 24 files changed

+1614/−0 lines, 24 filesdep +basedep +claydep +data-hassetup-changed

Dependencies added: base, clay, data-has, directory, frontmatter, inliterate, lucid, optparse-generic, process, temporary, text, tintin, universum, yaml

Files

+ ChangeLog.md view
@@ -0,0 +1,3 @@+# Changelog for tintin++## Unreleased changes
+ LICENSE.md view
@@ -0,0 +1,195 @@+Apache License+==============++_Version 2.0, January 2004_  +_&lt;<http://www.apache.org/licenses/>&gt;_++### Terms and Conditions for use, reproduction, and distribution++#### 1. Definitions++“License” shall mean the terms and conditions for use, reproduction, and+distribution as defined by Sections 1 through 9 of this document.++“Licensor” shall mean the copyright owner or entity authorized by the copyright+owner that is granting the License.++“Legal Entity” shall mean the union of the acting entity and all other entities+that control, are controlled by, or are under common control with that entity.+For the purposes of this definition, “control” means **(i)** the power, direct or+indirect, to cause the direction or management of such entity, whether by+contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the+outstanding shares, or **(iii)** beneficial ownership of such entity.++“You” (or “Your”) shall mean an individual or Legal Entity exercising+permissions granted by this License.++“Source” form shall mean the preferred form for making modifications, including+but not limited to software source code, documentation source, and configuration+files.++“Object” form shall mean any form resulting from mechanical transformation or+translation of a Source form, including but not limited to compiled object code,+generated documentation, and conversions to other media types.++“Work” shall mean the work of authorship, whether in Source or Object form, made+available under the License, as indicated by a copyright notice that is included+in or attached to the work (an example is provided in the Appendix below).++“Derivative Works” shall mean any work, whether in Source or Object form, that+is based on (or derived from) the Work and for which the editorial revisions,+annotations, elaborations, or other modifications represent, as a whole, an+original work of authorship. For the purposes of this License, Derivative Works+shall not include works that remain separable from, or merely link (or bind by+name) to the interfaces of, the Work and Derivative Works thereof.++“Contribution” shall mean any work of authorship, including the original version+of the Work and any modifications or additions to that Work or Derivative Works+thereof, that is intentionally submitted to Licensor for inclusion in the Work+by the copyright owner or by an individual or Legal Entity authorized to submit+on behalf of the copyright owner. For the purposes of this definition,+“submitted” means any form of electronic, verbal, or written communication sent+to the Licensor or its representatives, including but not limited to+communication on electronic mailing lists, source code control systems, and+issue tracking systems that are managed by, or on behalf of, the Licensor for+the purpose of discussing and improving the Work, but excluding communication+that is conspicuously marked or otherwise designated in writing by the copyright+owner as “Not a Contribution.”++“Contributor” shall mean Licensor and any individual or Legal Entity on behalf+of whom a Contribution has been received by Licensor and subsequently+incorporated within the Work.++#### 2. Grant of Copyright License++Subject to the terms and conditions of this License, each Contributor hereby+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,+irrevocable copyright license to reproduce, prepare Derivative Works of,+publicly display, publicly perform, sublicense, and distribute the Work and such+Derivative Works in Source or Object form.++#### 3. Grant of Patent License++Subject to the terms and conditions of this License, each Contributor hereby+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,+irrevocable (except as stated in this section) patent license to make, have+made, use, offer to sell, sell, import, and otherwise transfer the Work, where+such license applies only to those patent claims licensable by such Contributor+that are necessarily infringed by their Contribution(s) alone or by combination+of their Contribution(s) with the Work to which such Contribution(s) was+submitted. If You institute patent litigation against any entity (including a+cross-claim or counterclaim in a lawsuit) alleging that the Work or a+Contribution incorporated within the Work constitutes direct or contributory+patent infringement, then any patent licenses granted to You under this License+for that Work shall terminate as of the date such litigation is filed.++#### 4. Redistribution++You may reproduce and distribute copies of the Work or Derivative Works thereof+in any medium, with or without modifications, and in Source or Object form,+provided that You meet the following conditions:++* **(a)** You must give any other recipients of the Work or Derivative Works a copy of+this License; and+* **(b)** You must cause any modified files to carry prominent notices stating that You+changed the files; and+* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,+all copyright, patent, trademark, and attribution notices from the Source form+of the Work, excluding those notices that do not pertain to any part of the+Derivative Works; and+* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any+Derivative Works that You distribute must include a readable copy of the+attribution notices contained within such NOTICE file, excluding those notices+that do not pertain to any part of the Derivative Works, in at least one of the+following places: within a NOTICE text file distributed as part of the+Derivative Works; within the Source form or documentation, if provided along+with the Derivative Works; or, within a display generated by the Derivative+Works, if and wherever such third-party notices normally appear. The contents of+the NOTICE file are for informational purposes only and do not modify the+License. You may add Your own attribution notices within Derivative Works that+You distribute, alongside or as an addendum to the NOTICE text from the Work,+provided that such additional attribution notices cannot be construed as+modifying the License.++You may add Your own copyright statement to Your modifications and may provide+additional or different license terms and conditions for use, reproduction, or+distribution of Your modifications, or for any such Derivative Works as a whole,+provided Your use, reproduction, and distribution of the Work otherwise complies+with the conditions stated in this License.++#### 5. Submission of Contributions++Unless You explicitly state otherwise, any Contribution intentionally submitted+for inclusion in the Work by You to the Licensor shall be under the terms and+conditions of this License, without any additional terms or conditions.+Notwithstanding the above, nothing herein shall supersede or modify the terms of+any separate license agreement you may have executed with Licensor regarding+such Contributions.++#### 6. Trademarks++This License does not grant permission to use the trade names, trademarks,+service marks, or product names of the Licensor, except as required for+reasonable and customary use in describing the origin of the Work and+reproducing the content of the NOTICE file.++#### 7. Disclaimer of Warranty++Unless required by applicable law or agreed to in writing, Licensor provides the+Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,+including, without limitation, any warranties or conditions of TITLE,+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are+solely responsible for determining the appropriateness of using or+redistributing the Work and assume any risks associated with Your exercise of+permissions under this License.++#### 8. Limitation of Liability++In no event and under no legal theory, whether in tort (including negligence),+contract, or otherwise, unless required by applicable law (such as deliberate+and grossly negligent acts) or agreed to in writing, shall any Contributor be+liable to You for damages, including any direct, indirect, special, incidental,+or consequential damages of any character arising as a result of this License or+out of the use or inability to use the Work (including but not limited to+damages for loss of goodwill, work stoppage, computer failure or malfunction, or+any and all other commercial damages or losses), even if such Contributor has+been advised of the possibility of such damages.++#### 9. Accepting Warranty or Additional Liability++While redistributing the Work or Derivative Works thereof, You may choose to+offer, and charge a fee for, acceptance of support, warranty, indemnity, or+other liability obligations and/or rights consistent with this License. However,+in accepting such obligations, You may act only on Your own behalf and on Your+sole responsibility, not on behalf of any other Contributor, and only if You+agree to indemnify, defend, and hold each Contributor harmless for any liability+incurred by, or claims asserted against, such Contributor by reason of your+accepting any such warranty or additional liability.++_END OF TERMS AND CONDITIONS_++### APPENDIX: How to apply the Apache License to your work++To apply the Apache License to your work, attach the following boilerplate+notice, with the fields enclosed by brackets `[]` replaced with your own+identifying information. (Don't include the brackets!) The text should be+enclosed in the appropriate comment syntax for the file format. We also+recommend that a file or class name and description of purpose be included on+the same “printed page” as the copyright notice for easier identification within+third-party archives.++    Copyright 2018 Theam+    +    Licensed under the Apache License, Version 2.0 (the "License");+    you may not use this file except in compliance with the License.+    You may obtain a copy of the License at+    +      http://www.apache.org/licenses/LICENSE-2.0+    +    Unless required by applicable law or agreed to in writing, software+    distributed under the License is distributed on an "AS IS" BASIS,+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+    See the License for the specific language governing permissions and+    limitations under the License.+
+ README.md view
@@ -0,0 +1,40 @@+# Tintin++Tintin allows you to create fancy **plug 'n' play** documentation sites for your Haskell project very easily.++[Proceed to site](https://theam.github.io/tintin) to learn how to use Tintin.++# FAQ++## Why not just use Haddock?++Haddock provides a great interface for writing API documentation, which is a kind of *hard documentation*.+Newcomers from other languages are used to have a website per library, but sometimes this gets uncomfortable,+as maintaining a documentation website is more work for the author.++Tintin helps with this problem, as you get styling and organization for free. You just have to write your+documentation using Markdown.++The website generation is focused towards guides and tutorials, helping your users understand how to use your+package easily.++## Why not using ReadTheDocs or GitBook?++ReadTheDocs and GitBook are well established solutions for documentation, but they don't provide automatic+compilation and evaluation of Haskell code. This is error prone and can lead to confuse your users.++Tintin is tightly integrated with Haskell and its tools, helping you to document your library in a very easy+way.++# Projects using Tintin++Please submit a PR if you are using Tintin!++|λ|λ|+|-|-|+|[Tintin](https://theam.github.io/tintin)| A softer alternative to Haddock |++# Copyright++tintin - Copyright © 2018 [Theam](http://theam.io)+
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,32 @@+module Main where++import Universum++import Options.Generic++import Tintin+import Tintin.Core+import qualified Tintin.Capabilities.Logging as Logging+import qualified Tintin.Capabilities.Filesystem as Filesystem+import qualified Tintin.Capabilities.Process as Process+++data Options = Options+  { outputDirectory :: Maybe Text+  , verbose :: Bool+  }++deriving instance Generic Options+instance ParseRecord Options+++main :: IO ()+main = do+  opts <- getRecord "Tintin, the tutorial website generator"+  let outputDir = fromMaybe ".stack-work/tintin/rendered/" (outputDirectory opts)+  let logger     = if verbose opts+                   then Logging.stdOut+                   else Logging.mute+  let filesystem = Filesystem.local+  let process    = Process.local+  runEffects ( runApp $ OutputDirectory outputDir ) (logger, filesystem, process)
+ src/Tintin.hs view
@@ -0,0 +1,64 @@+module Tintin+  ( runApp+  )+where++import Tintin.Core++import qualified Tintin.Capabilities.Logging as Logging+import qualified Tintin.Capabilities.Filesystem as Filesystem+import qualified Tintin.Capabilities.Process as Process+import qualified Tintin.Parse as Parse+import qualified Tintin.Render as Render+import qualified Tintin.ConfigurationLoading as ConfigurationLoading+++runApp :: ( Has Logging.Capability eff+          , Has Filesystem.Capability eff+          , Has Process.Capability eff+          )+       => OutputDirectory+       -> Effectful eff ()+runApp outputDirectory = do+  cleanUp outputDirectory+  docDir    <- getDocumentationDirectory+  filenames <- getDocumentationFilenames docDir++  Parse.docs docDir filenames+   |>> Render.perform+   |>> ConfigurationLoading.loadInfo+   |>> Render.writeOutput outputDirectory+++cleanUp :: ( Has Logging.Capability eff+           , Has Filesystem.Capability eff+           )+        => OutputDirectory+        -> Effectful eff ()+cleanUp (OutputDirectory p) = do+  Logging.debug "Cleaning output directory"+  Filesystem.deleteIfExists (Filesystem.Path p)++++getDocumentationFilenames :: ( Has Logging.Capability eff+                             , Has Filesystem.Capability eff+                             )+                          => DocumentationDirectory+                          -> Effectful eff [Filesystem.Path]+getDocumentationFilenames (DocumentationDirectory docDir) = do+  Logging.debug ( "Reading documentation files at " <> docDir )+  Filesystem.Path docDir+   |>  Filesystem.list+   |$> Filesystem.getPathsWith (Filesystem.Extension ".md")++++getDocumentationDirectory :: Has Filesystem.Capability eff+                          => Effectful eff DocumentationDirectory+getDocumentationDirectory = do+  Filesystem.Path currentDir <- Filesystem.currentDirectory+  return ( DocumentationDirectory $ currentDir <> "/doc/" )+++
+ src/Tintin/Capabilities.hs view
@@ -0,0 +1,92 @@++module Tintin.Capabilities+  (+    {-|+'liftCapability' allows executing an 'IO' action stored in+a Capability value in the 'Effectful' monad.++It is intended to be used within the Capability definition+module, not outside.++@+data MyCapability = MyCapability+  { _myAction :: IO ()+  }++myAction :: Has MyCapability e+         => Effectful e ()+myAction = liftCapability _myAction+@+    -}+    liftCapability+  )+where++import Tintin.Core+++{-+This class should NOT be instantiated outside of this module,+as it serves to make the code more clean outside.++It allows us to do a++liftCapability f++instead of++liftCapability $ \capability -> f capability foo bar quux++where foo, bar, and quux are the remaining arguments.++These two instances allow the compiler to recurse on the arguments+and typecheck that the application is correct.++See: https://stackoverflow.com/q/50353294/3847023+-}++class LiftableWithCapability v where++  -- v must have an execution type like 'IO a'+  type Execution v :: *++  -- also, v must have a context available, to get the Capability from+  type Context v :: *++  -- We provide liftCapability, that ensures that it exists in the Context of v+  -- and, after passing a function that takes the capability and executes it in the+  -- Execution type of V+  liftCapability :: Has capability (Context v) => (capability -> Execution v) -> v++++-- We instantiate it for our Effectful type, which is an alias for ReaderT e IO a+instance LiftableWithCapability (ReaderT e IO a) where++  -- The execution type of that is 'IO a'+  type Execution (ReaderT e IO a) = IO a++  -- And the context is e+  type Context (ReaderT e IO a) = e++  -- liftCapability just grabs the capability and calls the function on it+  liftCapability f = do+    capability <- asks getter+    lift ( f capability )+++-- a function that takes some parameter a and returns v also can be lifted+instance LiftableWithCapability v => LiftableWithCapability (a -> v) where++  -- The execution type of the function, is just the Execution of the return type+  type Execution (a -> v) = a -> Execution v++  -- Same with the Context, the Context of the return type+  type Context (a -> v) = Context v++  -- We flip the arguments of f, so the capability record stays in the last place+  -- and we can recurse over it, until we reach the definition from before.+  --+  -- It is the same as doing 'liftCapability $ \x -> f x b'+  liftCapability f = liftCapability . flip f+
+ src/Tintin/Capabilities/Filesystem.hs view
@@ -0,0 +1,112 @@+module Tintin.Capabilities.Filesystem+  ( Capability+  , Path(..)+  , Extension(..)++  , local+  , deleteIfExists+  , list+  , currentDirectory+  , readFile+  , makeDirectory+  , writeFile+  , doesExist++  , getPathsWith+  )+where++import Tintin.Core hiding (list, local, readFile, writeFile)+import qualified Tintin.Core as Core+import Tintin.Capabilities++import qualified Data.Text as Text+++newtype Path      = Path Text deriving ( Show, Ord, Eq )+newtype Extension = Extension Text+++data Capability = Capability+  { _deleteIfExists   :: Path -> IO ()+  , _list             :: Path -> IO [Path]+  , _currentDirectory :: IO Path+  , _readFile         :: Path -> IO Text+  , _makeDirectory    :: Path -> IO ()+  , _writeFile        :: Path -> Text -> IO ()+  , _doesExist        :: Path -> IO Bool+  }+++local :: Capability+local =+  Capability {..}+ where+  _deleteIfExists (Path p) = do+    let pathToDelete = toString p+    pathExists <- doesDirectoryExist pathToDelete+    when pathExists (removeDirectoryRecursive pathToDelete)++  _list (Path p) = do+    files <- listDirectory (toString p)+    return $ sort (Path . toText <$> files)++  _currentDirectory =+    getCurrentDirectory+    |$> toText+    |$> Path++  _readFile (Path p) = Core.readFile (toString p)++  _makeDirectory (Path p) = createDirectoryIfMissing True (toString p)++  _writeFile (Path p) = Core.writeFile (toString p)++  _doesExist (Path p) = doesPathExist (toString p)+++deleteIfExists :: Has Capability eff+               => Path+               -> Effectful eff ()+deleteIfExists = liftCapability _deleteIfExists+++list :: Has Capability eff+     => Path+     -> Effectful eff [Path]+list = liftCapability _list+++currentDirectory :: Has Capability eff+                 => Effectful eff Path+currentDirectory = liftCapability _currentDirectory+++readFile :: Has Capability eff+         => Path+         -> Effectful eff Text+readFile = liftCapability _readFile+++makeDirectory :: Has Capability eff+              => Path+              -> Effectful eff ()+makeDirectory = liftCapability _makeDirectory+++writeFile :: Has Capability eff+          => Path+          -> Text+          -> Effectful eff ()+writeFile = liftCapability _writeFile+++doesExist :: Has Capability eff+          => Path+          -> Effectful eff Bool+doesExist = liftCapability _doesExist+++getPathsWith :: Extension -> [Path] -> [Path]+getPathsWith (Extension e) =+  filter (\(Path fn) -> e `Text.isSuffixOf` fn)
+ src/Tintin/Capabilities/Logging.hs view
@@ -0,0 +1,70 @@+module Tintin.Capabilities.Logging+  ( Capability+  , log+  , err+  , debug++  , stdOut+  , mute+  )+where++import Tintin.Core+import Tintin.Capabilities+++{- |+Capability for our context to be able to log from any place where it is available:++@+foo :: Has Logging.Capability e+    => Effectful e ()+foo = do+  log "Something happened :("+  ...+@+-}+data Capability = Capability+  { _log   :: Text -> IO ()+  , _err   :: Text -> IO ()+  , _debug :: Text -> IO ()+  }+++-- | Logs a text message using the available logger+log :: Has Capability e+    => Text+    -> Effectful e ()+log = liftCapability _log+++-- | Logs an error message using the available logger+err :: Has Capability e+    => Text+    -> Effectful e ()+err = liftCapability _err+++-- | Logs a debug message using the available logger+debug :: Has Capability e+      => Text+      -> Effectful e ()+debug = liftCapability _debug+++-- | Creates a logger that just prints things to STDOUT+stdOut :: Capability+stdOut = Capability+  { _log   = putTextLn+  , _err   = putTextLn . ("[ERROR] - " <>)+  , _debug = putTextLn . ("[DEBUG] - " <>)+  }+++-- | Creates a logger that does literally nothing+mute :: Capability+mute = Capability+  { _log = return . const ()+  , _debug = return . const ()+  , _err = return . const ()+  }
+ src/Tintin/Capabilities/Process.hs view
@@ -0,0 +1,44 @@+module Tintin.Capabilities.Process+  ( Capability+  , CommandName(..)+  , Arguments(..)+  , StdOut(..)+  , StdErr(..)++  , local+  , read+  )+where++import Tintin.Core hiding (stdout, stderr, local)+import Tintin.Capabilities++import System.Exit (ExitCode(..))+++newtype CommandName = CommandName Text+newtype Arguments   = Arguments [Text]+newtype StdOut      = StdOut Text+newtype StdErr      = StdErr Text++newtype Capability = Capability+  { _read :: CommandName -> Arguments -> IO (Either StdErr StdOut) +  }+++local :: Capability+local =+  Capability {..}+ where+  _read (CommandName cn) (Arguments args) = do+    result <- readProcessWithExitCode (toString cn) (toString <$> args) ""+    case result of+      (ExitSuccess, stdout, _) -> return (Right . StdOut $ toText stdout)+      (_, _, stderr) -> return (Left  . StdErr $ toText stderr)+++read :: Has Capability eff+           => CommandName+           -> Arguments+           -> Effectful eff (Either StdErr StdOut)+read = liftCapability _read
+ src/Tintin/ConfigurationLoading.hs view
@@ -0,0 +1,106 @@+module Tintin.ConfigurationLoading+  ( loadInfo )+where++import Tintin.Core+import qualified Tintin.Errors as Errors+import qualified Tintin.Capabilities.Logging as Logging+import qualified Tintin.Capabilities.Filesystem as Filesystem+import qualified Tintin.Domain.HtmlFile as HtmlFile+import qualified Tintin.Domain.Project as Project++import qualified Data.Text as Text+import Universum.Unsafe (fromJust)+import Text.Read (read)++import qualified Universum.Debug as Debug++loadInfo :: ( Has Logging.Capability eff+            , Has Filesystem.Capability eff+            )+         => [HtmlFile.Value]+         -> Effectful eff Project.Info+loadInfo htmlFiles = do+  let pages = htmlFiles+              |> map (\HtmlFile.Value {..} -> Project.Page title content filename)+  Filesystem.Path currentDir <- Filesystem.currentDirectory+  files <- Filesystem.list (Filesystem.Path currentDir)+  let packageYamlFile = find isPackageYaml files+  let cabalFile       = find isCabalFile files+  case packageYamlFile <|> cabalFile of+    Nothing -> do+      Errors.showAndDie ["No package.yaml or *.cabal file found."]+      error ""++    Just p -> do+      let tintinPath = Filesystem.Path $ currentDir <> "/.tintin.yml"+      Logging.debug "Reading project info"+      projectInfoFile <- Filesystem.readFile p+      tintinExists    <- Filesystem.doesExist tintinPath+      unless tintinExists $+        Filesystem.writeFile tintinPath "color: blue\n"+      tintinFile <- Filesystem.readFile tintinPath+      let+        projectName     = projectInfoFile |> getFieldValue "name"+        projectSynopsis = projectInfoFile |> getFieldValue "synopsis"+        projectGithub   = (projectInfoFile |> getFieldValue "github")+                          <|> (projectInfoFile |> getFieldValue "location")+        projectAuthor   = projectGithub |$> getAuthor+        tintinColor     = tintinFile |> getFieldValue "color"+        tintinLogo      = tintinFile |> getFieldValue "logo"+      when (isNothing projectName) (Errors.showAndDie ["Project must have a name. Please set it in package.yaml or *.cabal."])+      when (isNothing projectSynopsis) (Errors.showAndDie ["Project must have a synopsis. Please set it in package.yaml or *.cabal."])+      when (isNothing projectGithub) (Errors.showAndDie ["Project must be hosted in a Github repository. Please set it in package.yaml or *.cabal."])+      when (isNothing tintinColor)+        (Errors.showAndDie ["Tintin usually generates a .tintin.yml file with a color configuration. Maybe you don't have enough permissions?\+                           \\n\nTry creating .tintin.yml and adding color:blue to it."])+      return Project.Info+        { name = fromJust projectName+        , synopsis = fromJust projectSynopsis+        , githubLink = parseGithubUrl $ fromJust projectGithub+        , githubAuthor = fromJust projectAuthor+        , color = makeColor $ fromJust tintinColor+        , logoUrl = tintinLogo+        , pages = pages+        }++ where+  isPackageYaml (Filesystem.Path p) =+    p == "package.yaml"++  isCabalFile   (Filesystem.Path p) =+    ".cabal" `Text.isInfixOf` p++  makeColor :: Text -> Project.Color+  makeColor txt =+    let capitalLetter = txt+                        |> Text.head+                        |> Text.singleton+                        |> Text.toUpper+        restOfText    = txt+                        |> Text.tail+    in  (capitalLetter <> restOfText)+         |> toString+         |> read++  getFieldValue field txt = txt+                          |> lines+                          |> filter (\t -> field `Text.isPrefixOf` Text.strip t)+                          |> safeHead+                          |$> Text.strip+                          |>> Text.stripPrefix (field <> ":")+                          |$> Text.strip+  getAuthor txt =+    txt+    |> (Text.stripPrefix "\"" >=> Text.stripSuffix "\"")+    |> fromMaybe txt+    |> Text.takeWhile (/= '/')++  parseGithubUrl txt =+    txt+    |>  Text.stripPrefix "\""+    |>> Text.stripSuffix "\""+    |>  fromMaybe txt+++
+ src/Tintin/Core.hs view
@@ -0,0 +1,45 @@+module Tintin.Core+  ( module Exported++  , Effectful+  , Pure++  , (|>)+  , (|$>)+  , (|>>)++  , runEffects+  )+where++import Universum as Exported hiding (log)++import Data.Has as Exported+import System.Directory as Exported+import System.IO.Temp as Exported+import System.Process as Exported++import Tintin.Domain as Exported++++type Effectful context result =+  ReaderT context IO result++runEffects :: Effectful context result+           -> context+           -> IO result+runEffects = runReaderT++type Pure context result =+  Reader context result+++(|>) :: a -> (a -> b) -> b+(|>) = (&)++(|$>) :: Functor f => f a -> (a -> b) -> f b+(|$>) = (<&>)++(|>>) :: Monad m => m a -> (a -> m b) -> m b+(|>>) = (>>=)
+ src/Tintin/Domain.hs view
@@ -0,0 +1,8 @@+module Tintin.Domain where++import Universum+++newtype DocumentationDirectory = DocumentationDirectory Text+newtype OutputDirectory        = OutputDirectory Text+
+ src/Tintin/Domain/DocumentationFile.hs view
@@ -0,0 +1,40 @@+module Tintin.Domain.DocumentationFile where++import Tintin.Core++import qualified Data.Frontmatter as FMParser++import Tintin.Domain.FrontMatter as FrontMatter+++newtype Filename   = Filename Text+newtype ParseError = ParseError Text deriving Show++data Value = Value+  { filename    :: Text+  , content     :: Text+  , frontMatter :: FrontMatter+  } deriving Show+++new :: Filename -> Text -> Either ParseError Value+new (Filename filename) rawText =+  case parse rawText of+    FMParser.Fail _ _ err ->+      Left ( ParseError $ "Parse error on "+             <> filename+             <> " no front matter found."+           )++    FMParser.Done source frontMatter ->+      Right $ Value+        { filename    = filename+        , content     = decodeUtf8 @Text @ByteString source+        , frontMatter = frontMatter+        }+ where+  parse txt =+    encodeUtf8 @Text @ByteString txt+    |> FMParser.parseYamlFrontmatter++
+ src/Tintin/Domain/FrontMatter.hs view
@@ -0,0 +1,22 @@+module Tintin.Domain.FrontMatter where++import Tintin.Core++import Data.Yaml+++data FrontMatter = FrontMatter+  { title :: Text+  , subtitle :: Maybe Text+  , layout :: Maybe Text+  } deriving Show+++instance FromJSON FrontMatter where+  parseJSON (Object v) =+    FrontMatter+    <$> v .: "title"+    <*> v .:? "subtitle"+    <*> v .:? "layout"+  parseJSON _ = fail "Expected object"+
+ src/Tintin/Domain/HtmlFile.hs view
@@ -0,0 +1,68 @@+module Tintin.Domain.HtmlFile where++import Tintin.Core+import qualified Tintin.Capabilities.Filesystem as Filesystem+import qualified Tintin.Capabilities.Process as Process+import qualified Tintin.Domain.DocumentationFile as DocumentationFile+import qualified Tintin.Domain.FrontMatter as FrontMatter++import qualified Data.Text as Text+++newtype CompilationError = CompilationError Text deriving Show++data Value = Value+  { filename :: Text+  , title    :: Text+  , content  :: Text+  }+++fromDocumentationFile :: DocumentationFile.Value+                      -> Value+fromDocumentationFile docfile =+  DocumentationFile.content docfile+   |> ("{-# OPTIONS_GHC -F -pgmF inlitpp #-}\n" <>)+   |> Value (DocumentationFile.filename docfile) docTitle+ where+  docTitle = docfile |> DocumentationFile.frontMatter |> FrontMatter.title+++run :: ( Has Filesystem.Capability eff+       , Has Process.Capability eff+       )+    => Value+    -> Effectful eff (Either CompilationError Value)+run Value {..} = do+  Filesystem.Path currentDirectory <- Filesystem.currentDirectory+  let tintinDir = currentDirectory <> "/.stack-work/tintin/"+  let tempDir   = tintinDir <> "temp/"+  let hsFilename = filename+                   |> Text.breakOn ".md"+                   |> fst+                   |> (<> ".hs")+                   |> (tempDir <>)+  let htmlFilename = filename+                     |> Text.breakOn ".md"+                     |> fst+                     |> (<> ".html")+  Filesystem.deleteIfExists (Filesystem.Path tempDir)+  Filesystem.makeDirectory (Filesystem.Path tempDir)+  Filesystem.writeFile (Filesystem.Path hsFilename) content+  result <- Process.read+    (Process.CommandName "stack")+    (Process.Arguments ["runghc", hsFilename, "--", "--no-inlit-wrap"])+++  case result of+    Left  (Process.StdErr err) ->+      return (Left $ CompilationError err)++    Right (Process.StdOut msg) ->+      return . Right $ Value+        { filename = htmlFilename+        , content  = msg+        , title    = title+        }++
+ src/Tintin/Domain/Project.hs view
@@ -0,0 +1,35 @@+module Tintin.Domain.Project where++import Tintin.Core++data Color+  = Purple+  | LightGreen+  | DarkGreen+  | Blue+  | DarkBlue+  | Bronze+  | DarkOrange+  | LightOrange+  | Red+  | Grey+  deriving (Show, Read)+++data Page = Page+  { title    :: Text+  , content  :: Text+  , filename :: Text+  }+++data Info = Info+  { name         :: Text+  , synopsis     :: Text+  , color        :: Color+  , githubLink   :: Text+  , githubAuthor :: Text+  , logoUrl      :: Maybe Text+  , pages        :: [Page]+  }+
+ src/Tintin/Errors.hs view
@@ -0,0 +1,19 @@+module Tintin.Errors+  ( showAndDie+  )+where++import Tintin.Core+import qualified Tintin.Capabilities.Logging as Logging+++showAndDie :: ( Has Logging.Capability eff+              , Show error+              )+           => [error]+           -> Effectful eff ()+showAndDie errors = do+    errors+     |> mapM_ (Logging.err . show)+    error "Errors found. Exiting."+
+ src/Tintin/Html/Style.hs view
@@ -0,0 +1,235 @@+module Tintin.Html.Style where++import Tintin.Core as Core hiding (( & ), rem, (|>))++import Clay+import qualified Clay.Media as Media+import Clay.Selector++style :: Text+style = toText . render $ do+  html ? do+    height (pct 100)+    minHeight (pct 100)++  body ? do+    height (pct 100)+    minHeight (pct 100)+    fontFamily ["IBM Plex Sans"] [ sansSerif ]+    fontSize (em 1)+    overflowX hidden++  forM_ (zip [(0::Double)..] [h1, h2, h3]) $ \(n, x) -> x ? do+    fontFamily ["Montserrat" ] [ sansSerif ]+    fontWeight bold+    fontSize (em (2.441 Core.** n))++  h1 ? fontSize (em 2.441)+  h2 ? fontSize (em 1.953)+  h3 ? fontSize (em 1.563)++  "#header-container" ? do+    marginTop (rem 5)+    marginBottom (rem 5)++  ".cover-heading" ? do+    fontSize (pct 800)+    maxHeight (rem 6)+    marginBottom (rem 1.563)++  ".cover-container" ? do+   backgroundColor (rgba 255 255 255 0.0)++  ".watermark" ? do+    position absolute+    top (px 0)+    left (px 0)+    maxHeight (rem 1.2504)+    marginTop (rem 1.2504)+    marginLeft (rem 1.2504)++  ".cover-heading-subtitle" ? do+    fontSize (rem 1.953)++  ".vertical-auto" ? do+    marginTop auto+    marginBottom auto++  ".content" ? do+    marginTop (pct 5)+    marginBottom (pct 5)++  "#wrapper" ? do+    paddingLeft (px 0)+    transition "all" (sec 0.5) ease (sec 0)+    ".toggled" & do+      paddingLeft (px 250)+      "#sidebar-wrapper" ? do+        width (px 250)+      "#page-content-wrapper" & do+        position absolute+        marginRight (px (-250))++  "#page-content-wrapper" ? do+    width (pct 100)+    position absolute+    marginTop (rem 3)+    padding (px 15) (px 15) (px 15) (px 15)+    img ? do+      maxWidth (pct 100)++  "#sidebar-wrapper" ? do+    zIndex 1000+    position fixed+    left (px 250)+    width (px 0)+    marginLeft (px (-250))+    overflowY hidden+    overflowX hidden+    transition "all" (sec 0.5) ease (sec 0)+++  ".sidebar-nav" ? do+    position absolute+    top (px 0)+    width (px 250)+    margin (px 0) (px 0) (px 0) (px 0)+    padding (px 0) (px 0) (px 0) (px 0)+    listStyleType none+    "li" ? do+      textIndent (indent $ px 20)+      lineHeight (px 40)+      "a" ? do+        display block+        textDecoration none+        fontWeight bold+        ":hover" & do+          textDecoration none+          color white+        ":active" & do+          textDecoration none+          color white+        ":focus" & do+          textDecoration none+          color white++  ".tintin-doc-topbar" ? do+    height (rem 5)+    a ? do+      marginLeft (rem 1)+      width (rem 1.5)+      img ? do+        Clay.filter (invert $ pct 70)++  ".filter-gray" ? do+    position relative+    bottom (px ( -3 ))+    marginLeft (rem 0.25)+    marginRight (rem 1)+    height (rem 1)+    Clay.filter (brightness 0.75)++  ".footer-theam" ? do+    position relative+    bottom (px $ 1)+    marginLeft (rem $ -0.25)+    height (rem 1.75)+    Clay.filter (invert (pct 75))+    -- Clay.filter (brightness 0.75)++  ".tintin-doc-footer" ? do+    bottom (px 0)+    height (rem $ -15)+    width (pct 100)+    color (rgba 0 0 0 0.30)++  ".main-container" ? do+    minHeight (pct 100)+    position relative++  "#content" ? do+    minHeight (pct 95)++  ".tintin-generated-with" ? do+    pass++  ".sidebar-nav" |> ".sidebar-brand" ? do+    height (rem 3)+    fontSize (rem 2)+    fontFamily ["Montserrat"] [sansSerif]+    fontWeight bold+    paddingTop (rem 2.5)+    paddingBottom (rem 2.5)+    marginBottom (rem 1.5)+    img ? do+      height (rem 1.5)++  ".tintin-navbar" ? do+    fontWeight bold+    backgroundColor (rgba 255 255 255 0.15)++  ".tintin-bg-70" ? do+    backgroundColor (rgba 255 255 255 0.15)++  forM colorNames $ \(colorName, colorValue) ->+    (text $ ".tintin-bg-" <> colorName) ? do+      backgroundColor colorValue++  forM colorNames $ \(colorName, colorValue) ->+    (text $ ".tintin-fg-" <> colorName) ? do+      color colorValue++  ".tintin-fg-active" ? do+    color (rgba 255 255 255 1.0)++  ".tintin-fg-disabled" ? do+    color (rgba 255 255 255 0.35)++  footer ? do+    position relative+    bottom (px 0)+    left (px 0)+    width (pct 100)+    paddingTop (px 30)+    paddingBottom (px 30)++  ".container" ? do+    maxWidth (rem 50)++  query Media.screen [Media.minWidth (px 768)] $ do+    "#wrapper" ? do+      paddingLeft (px 0)+      ".toggled" ? do+        paddingLeft (px 250)+        "#sidebar-wrapper" ? do+          width (px 250)+        "#page-content-wrapper" ? do+          position relative+          marginRight (px 0)++    "#sidebar-wrapper" ? do+      width (px 0)++    "#page-content-wrapper" ? do+      padding (px 20) (px 20) (px 20) (px 20)+      position relative+++++colorNames :: [(Text, Color)]+colorNames =+  [ ("black"      , "#1d1f21")+  , ("white"      , "#f5f8f6")+  , ("grey"       , "#4D4D4D")+  , ("red"        , "#D30228")+  , ("darkgreen"  , "#3C8B6A")+  , ("lightgreen" , "#A4CB58")+  , ("darkorange" , "#FF6602")+  , ("lightorange", "#FAA73E")+  , ("blue"       , "#94C1E8")+  , ("darkblue"   , "#007C99")+  , ("purple"     , "#9F76B4")+  , ("bronze"     , "#A4A27A")+  , ("darkgrey"   , "#282a2e")+  ]
+ src/Tintin/Html/Templating.hs view
@@ -0,0 +1,198 @@+module Tintin.Html.Templating where++import Lucid++import Tintin.Core+import qualified Tintin.Html.Style as Style+import qualified Tintin.Domain.Project as Project++import qualified Data.Text as Text+++asset :: Text -> Text+asset txt = "https://s3-eu-west-1.amazonaws.com/worldwideapps/assets/" <> txt++wrap :: Project.Info -> Project.Page -> Text+wrap info page =+  if (Project.filename page) == "index.html"+  then wrapHome info page+  else wrapPage info page+++wrapPage :: Project.Info -> Project.Page -> Text+wrapPage info page = toText . renderText $ do+  tintinHeader info page+  body_ [class_ "h-100 tintin-fg-black tintin-bg-white"] $ do+    div_ [id_ "main-container", class_ "h-100"] $ do+      section_ [ id_ "content"] $ do+        div_ [id_ "wrapper", class_ "toggled"] $ do+          div_ [id_ "sidebar-wrapper", class_ $ "h-100 tintin-bg-" <> bgColorOf info] $ do+            div_ [ class_ "h-100 tintin-bg-70"] ( p_ "" )+            ul_ [ class_ "sidebar-nav"] $ do+              li_ [ class_ $ "sidebar-brand d-flex tintin-bg-" <> bgColorOf info] $ do+                a_ [href_ "index.html", class_ "align-self-center tintin-fg-white"] $ do+                  case Project.logoUrl info of+                    Nothing -> toHtml $ Project.name info+                    Just url -> img_ [src_ url]+              forM_ (filter (\p -> "index.html" /= Project.filename p ) (Project.pages info) ) $ \p -> do+                li_ $ do+                  let classes =+                        if Project.title page == Project.title p+                        then "tintin-fg-active"+                        else "tintin-fg-disabled"+                  a_ [href_ $ Project.filename p, class_ classes] $ do+                    ( toHtml $ Project.title p )++          nav_ [class_ "navbar navbar-expand-lg tintin-doc-topbar tintin-fg-white"] $ do+            a_ [id_ "menu-open", href_ "#menu-toggle", class_ "d-none"] $+              img_ [ src_ $ asset "menu.png", class_ "img-fluid" ]+            a_ [id_ "menu-close", href_ "#menu-toggle"] $+              img_ [ src_ $ asset "close.png", class_ "img-fluid" ]++          div_ [id_ "page-content-wrapper"] $ do+            div_ [class_ "container"] $ do+              div_ [class_ "col"] $+                div_ [ data_ "aos" "fade-left"+                     , data_ "aos-duration" "800"+                     , data_ "aos-once" "true"+                     ] $+                  toHtmlRaw $ Project.content page+      div_ [class_ "tintin-doc-footer clear-fix"]+        siteGenerated+    tintinPostInit++siteGenerated = do+  div_ [class_ "float-right"] $ do+    div_ [class_ "d-inline", style_ "float: left"] $ do+      p_ [class_ ""] "Site generated with "+    a_ [ style_ "float: left", href_ "https://theam.github.io/tintin"] $ do+      img_ [ class_ "filter-gray", src_ $ asset "logo.svg" ]+    div_ [class_ "clear-fix float-right"] $ do+      span_ [style_ "float:left"] $ toHtmlRaw ("&mdash; &copy; 2018 " :: Text)+      a_ [class_ "float:left", href_ "http://theam.io"] $+        img_ [class_ "footer-theam", src_ "http://theam.io/logo_theam.png"]+++wrapHome :: Project.Info -> Project.Page -> Text+wrapHome info page = toText . renderText $ do+  tintinHeader info page+  body_ [class_ "tintin-fg-black tintin-bg-white"] $ do++    div_ [class_ $ "tintin-navigation tintin-bg-" <> bgColorOf info] $ do+      div_ [class_ "cover-container d-flex p-3 mx-auto flex-column tintin-fg-white"] $ do+        main_ [role_ "main", class_ "masthead mb-auto"] $ do+          div_ [class_ "container"] $ do+            div_ [class_ "row align-items-center"] $ do+              div_ [class_ "col", id_ "header-container"] $ do+                case Project.logoUrl info of+                  Just url -> img_ [ src_ url, class_ "cover-heading" ]+                  Nothing -> h1_ [class_ "cover-heading"] $ toHtml (Project.name info)+                p_ [class_ "cover-heading-subtitle"] (toHtml $ Project.synopsis info)++      navbar++    section_ [ id_ "content"+             , data_ "aos" "fade-up"+             , data_ "aos-duration" "800"+             , data_ "aos-once" "true"+             ] $ do+      div_ [class_ "container"] $ do+        div_ [class_ "content"+             ] $+          div_ [] $ do+            toHtmlRaw $ Project.content page+    footer+    tintinPostInit+ where+  navbar =+    nav_ [ class_ "navbar navbar-expand-lg navbar-dark tintin-navbar"+         , style_ "width: 100%;"+         ] $ do+      div_ [ class_ "container" ] $ do+        div_ [class_ "collapse navbar-collapse", id_ "navbarSupportedContent"] $ do+          ul_ [class_ "navbar-nav mr-auto"] $ do+            li_ [class_ "nav-item active"] $+              a_ [class_ "nav-link active", href_ "index.html"] "Home"+            let (page:_) = filter (\x -> "index.html" /= Project.filename x ) (Project.pages info)+            li_ [class_ "nav-item"] $+              a_ [class_ "nav-link", href_ (Project.filename page)] "Docs"+        div_ $+          ul_ [class_ "navbar-nav mr-sm-2"] $+            li_ [class_ "nav-item"] $+              a_ [class_ "nav-link", href_ $ "https://github.com/" <> Project.githubLink info] "View on GitHub"++  footer =+    footer_ [ class_ "tintin-bg-darkgrey tintin-fg-white"] $+      div_ [class_ "container"] $+        div_ [class_ "row"] $ do+          div_ [class_ "col"] $+            p_ [class_ "tintin-fg-lightgrey"] $ do+              "Developed by "+              a_ [ href_ $ "https://github.com/" <> Project.githubAuthor info+                 , class_ $ "tintin-fg-" <> bgColorOf info+                 ] (toHtml $ Project.githubAuthor info)+          div_ [class_ "col", style_ ""] $ do+            siteGenerated+++tintinHeader :: Project.Info -> Project.Page -> Html ()+tintinHeader info@Project.Info {..} Project.Page {..} =+  head_ $ do+    title_ ( toHtml $ name <> " - " <> title )+    link_ [ rel_ "stylesheet"+          , href_ "https://cdn.rawgit.com/michalsnik/aos/2.1.1/dist/aos.css"+          ]+    link_ [ rel_ "stylesheet"+          , href_ "https://fonts.googleapis.com/css?family=IBM+Plex+Sans|Montserrat:500"+          ]+    link_ [ rel_ "stylesheet"+          , href_ "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"+          ]+    link_ [ rel_ "stylesheet"+          , href_ "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/tomorrow-night.min.css"+          ]+    link_ [ rel_ "shortcut icon"+          , href_ ( asset $ "favicon-"+                  <> bgColorOf info+                  <> ".ico"+                  )+          ]+    style_ Style.style+++tintinPostInit :: Html ()+tintinPostInit = do+  script_ [ src_ "https://code.jquery.com/jquery-3.2.1.slim.min.js" ] ( "" :: Text )+  script_ [ src_ "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"] ( "" :: Text )+  script_ [ src_ "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"] ( "" :: Text )+  script_ [ src_ "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"] ( "" :: Text )+  script_ [ src_ "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/languages/haskell.min.js"] ("" :: Text)+  script_ [ src_ "https://cdn.rawgit.com/icons8/bower-webicon/v0.10.7/jquery-webicon.min.js" ] ( "" :: Text )+  script_ "$(function() { AOS.init(); })"+  script_ [ src_ "https://cdn.rawgit.com/michalsnik/aos/2.1.1/dist/aos.js"] ("" :: Text)+  script_ [ src_ "https://cdnjs.cloudflare.com/ajax/libs/animejs/2.2.0/anime.min.js" ] ("" :: Text)+  script_ "hljs.initHighlightingOnLoad()"+  script_ "$(function () {$(\"#menu-open\").click(function(e) {\+        \e.preventDefault();\+        \$(\"#wrapper\").toggleClass(\"toggled\");\+        \anime({targets: \"#menu-open img\", scale: 0.125, rotate: '0deg'});\+        \$(\"#menu-close\").toggleClass(\"d-none\");\+        \$(\"#menu-open\").toggleClass(\"d-none\");\+        \anime({targets: \"#menu-close img\", scale: 1, rotate: '180deg'});\+    \})});"+  script_ "$(function () {$(\"#menu-close\").click(function(e) {\+        \e.preventDefault();\+        \$(\"#wrapper\").toggleClass(\"toggled\");\+        \anime({targets: \"#menu-close img\", scale: 0.125, rotate: '0deg'});\+        \$(\"#menu-close\").toggleClass(\"d-none\");\+        \$(\"#menu-open\").toggleClass(\"d-none\");\+        \anime({targets: \"#menu-open img\", scale: 1, rotate: '180deg'});\+    \})});"+++bgColorOf :: Project.Info -> Text+bgColorOf info =+  Project.color info+  |> show+  |> Text.toLower+
+ src/Tintin/Parse.hs view
@@ -0,0 +1,39 @@+module Tintin.Parse+  ( docs )+where++import Tintin.Core+import qualified Tintin.Capabilities.Logging as Logging+import qualified Tintin.Capabilities.Filesystem as Filesystem+import qualified Tintin.Domain.DocumentationFile as DocumentationFile+import qualified Tintin.Errors as Errors+++docs :: ( Has Logging.Capability eff+        , Has Filesystem.Capability eff+        )+     => DocumentationDirectory+     -> [Filesystem.Path]+     -> Effectful eff [DocumentationFile.Value]+docs docDir filenames = do+  Logging.debug "Parsing documentation"+  (errors, docFiles) <- filenames+                        |>  mapM (readAndParse docDir)+                        |$> partitionEithers+  unless (null errors) (Errors.showAndDie errors)+  return docFiles+++readAndParse :: ( Has Logging.Capability eff+                , Has Filesystem.Capability eff+                )+             => DocumentationDirectory+             -> Filesystem.Path+             -> Effectful eff (Either DocumentationFile.ParseError DocumentationFile.Value)+readAndParse ( DocumentationDirectory d ) ( Filesystem.Path f ) = do+  contents <- Filesystem.readFile ( Filesystem.Path $ d <> "/" <> f)+  return $ DocumentationFile.new (DocumentationFile.Filename f) contents++++
+ src/Tintin/Render.hs view
@@ -0,0 +1,45 @@+module Tintin.Render where++import Tintin.Core+import Tintin.Capabilities.Logging as Logging+import Tintin.Capabilities.Filesystem as Filesystem+import Tintin.Capabilities.Process as Process+import Tintin.Domain.HtmlFile as HtmlFile+import Tintin.Domain.DocumentationFile as DocumentationFile+import Tintin.Domain.Project as Project+import Tintin.Html.Templating as Templating+import Tintin.Errors as Errors++import qualified Data.Text as Text++perform :: ( Has Logging.Capability eff+           , Has Filesystem.Capability eff+           , Has Process.Capability eff+           )+        => [DocumentationFile.Value]+        -> Effectful eff [HtmlFile.Value]+perform docFiles = do+  Logging.debug "Rendering"+  (errors, htmlFiles) <- docFiles+                         |>  map  HtmlFile.fromDocumentationFile+                         |>  mapM HtmlFile.run+                         |$> partitionEithers+  unless (null errors) (Errors.showAndDie errors)+  return htmlFiles+++writeOutput :: ( Has Logging.Capability eff+               , Has Filesystem.Capability eff+               )+            => OutputDirectory+            -> Project.Info+            -> Effectful eff ()+writeOutput (OutputDirectory od) info = do+  Filesystem.makeDirectory (Filesystem.Path od)+  Logging.debug "Writing HTML output"+  forM_ (Project.pages info) $ \page -> do+    let newContent = Templating.wrap info page+    let slash = if "/" `Text.isSuffixOf` od then "" else "/"+    Filesystem.writeFile (Filesystem.Path $ od <> slash <> Project.filename page) newContent++
+ test/Spec.hs view
@@ -0,0 +1,4 @@+import Tintin.Core++main :: IO ()+main = putTextLn "Test suite not yet implemented"
+ tintin.cabal view
@@ -0,0 +1,96 @@+-- This file has been generated from package.yaml by hpack version 0.27.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: cd2984d0babd6cb9da9ae174fd16b41f7dae8f9b6187282ae4278a91ceabc171++name:           tintin+version:        1.0.3+synopsis:       A softer alternative to Haddock+description:    Please see the website <https://theam.github.io/tintin>+category:       Documentation+homepage:       https://github.com/theam/tintin#readme+bug-reports:    https://github.com/theam/tintin/issues+author:         The Agile Monkeys+maintainer:     hackers@theam.io+copyright:      2018 Theam+license:        Apache-2.0+license-file:   LICENSE.md+build-type:     Simple+cabal-version:  >= 1.10++extra-source-files:+    ChangeLog.md+    README.md++source-repository head+  type: git+  location: https://github.com/theam/tintin++library+  exposed-modules:+      Tintin+      Tintin.Capabilities+      Tintin.Capabilities.Filesystem+      Tintin.Capabilities.Logging+      Tintin.Capabilities.Process+      Tintin.ConfigurationLoading+      Tintin.Core+      Tintin.Domain+      Tintin.Domain.DocumentationFile+      Tintin.Domain.FrontMatter+      Tintin.Domain.HtmlFile+      Tintin.Domain.Project+      Tintin.Errors+      Tintin.Html.Style+      Tintin.Html.Templating+      Tintin.Parse+      Tintin.Render+  other-modules:+      Paths_tintin+  hs-source-dirs:+      src+  default-extensions: DeriveGeneric RecordWildCards OverloadedStrings DataKinds TypeOperators NoImplicitPrelude StandaloneDeriving TypeApplications KindSignatures FlexibleContexts FlexibleInstances MultiParamTypeClasses TypeFamilies+  build-depends:+      base >=4.7 && <5+    , clay+    , data-has+    , directory+    , frontmatter+    , inliterate+    , lucid+    , process+    , temporary+    , text+    , universum+    , yaml+  default-language: Haskell2010++executable tintin+  main-is: Main.hs+  other-modules:+      Paths_tintin+  hs-source-dirs:+      app+  default-extensions: DeriveGeneric RecordWildCards OverloadedStrings DataKinds TypeOperators NoImplicitPrelude StandaloneDeriving TypeApplications KindSignatures FlexibleContexts FlexibleInstances MultiParamTypeClasses TypeFamilies+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.7 && <5+    , optparse-generic+    , tintin+    , universum+  default-language: Haskell2010++test-suite tintin-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Paths_tintin+  hs-source-dirs:+      test+  default-extensions: DeriveGeneric RecordWildCards OverloadedStrings DataKinds TypeOperators NoImplicitPrelude StandaloneDeriving TypeApplications KindSignatures FlexibleContexts FlexibleInstances MultiParamTypeClasses TypeFamilies+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.7 && <5+    , tintin+  default-language: Haskell2010