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 +3/−0
- LICENSE.md +195/−0
- README.md +40/−0
- Setup.hs +2/−0
- app/Main.hs +32/−0
- src/Tintin.hs +64/−0
- src/Tintin/Capabilities.hs +92/−0
- src/Tintin/Capabilities/Filesystem.hs +112/−0
- src/Tintin/Capabilities/Logging.hs +70/−0
- src/Tintin/Capabilities/Process.hs +44/−0
- src/Tintin/ConfigurationLoading.hs +106/−0
- src/Tintin/Core.hs +45/−0
- src/Tintin/Domain.hs +8/−0
- src/Tintin/Domain/DocumentationFile.hs +40/−0
- src/Tintin/Domain/FrontMatter.hs +22/−0
- src/Tintin/Domain/HtmlFile.hs +68/−0
- src/Tintin/Domain/Project.hs +35/−0
- src/Tintin/Errors.hs +19/−0
- src/Tintin/Html/Style.hs +235/−0
- src/Tintin/Html/Templating.hs +198/−0
- src/Tintin/Parse.hs +39/−0
- src/Tintin/Render.hs +45/−0
- test/Spec.hs +4/−0
- tintin.cabal +96/−0
+ 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_ +_<<http://www.apache.org/licenses/>>_++### 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 ("— © 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