packages feed

tailwind (empty) → 0.1.0.0

raw patch · 6 files changed

+340/−0 lines, 6 filesdep +aesondep +asyncdep +base

Dependencies added: aeson, async, base, bytestring, containers, data-default, deriving-aeson, directory, filepath, filepattern, lens, monad-logger, mtl, neat-interpolation, optparse-applicative, parser-combinators, profunctors, relude, safe-exceptions, tailwind, temporary, text, time, unliftio, which, with-utf8

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for tailwind (Haskell)++## 0.1.0.0 -- 2022-01-08++* First version.
+ LICENSE view
@@ -0,0 +1,21 @@+MIT License++Copyright (c) 2021 Sridhar Ratnakumar++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ README.md view
@@ -0,0 +1,11 @@+# tailwind++Run TailwindCSS CLI [without needing](https://www.srid.ca/nojs) to touch anything JavaScript. No `input.css` or `tailwind.config.js` necessary.++```+cabal run tailwind-run -- 'src/**/.hs'+```++Compiles CSS classes in those file paths or patterns, and writes `./tailwind.css`. Pass `-w` to run in JIT watcher mode.++Used in Emanote, and Ema apps.
+ exe/Main.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE ApplicativeDo #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeApplications #-}++module Main where++import qualified Control.Applicative.Combinators.NonEmpty as NE+import Control.Lens.Operators+import Control.Monad.Logger.CallStack (runStdoutLoggingT)+import Data.Default (Default (def))+import Main.Utf8 (withUtf8)+import Options.Applicative+import System.FilePattern (FilePattern)+import Web.Tailwind++data Cli = Cli+  { content :: NonEmpty FilePattern,+    mode :: Mode+  }+  deriving (Eq, Show)++cliParser :: Parser Cli+cliParser = do+  content <- NE.some (argument str (metavar "SOURCES..."))+  mode <- modeParser+  pure Cli {..}++modeParser :: Parser Mode+modeParser = do+  bool Production JIT <$> switch (long "watch" <> short 'w' <> help "Run JIT")++main :: IO ()+main = do+  withUtf8 $ do+    cli <- execParser opts+    print cli+    runStdoutLoggingT $+      runTailwind $+        def+          & tailwindConfig . tailwindConfigContent .~ toList (content cli)+          & tailwindMode .~ mode cli+  where+    opts =+      info+        (cliParser <**> helper)+        ( fullDesc+            <> progDesc "Run Tailwind"+        )
+ src/Web/Tailwind.hs view
@@ -0,0 +1,168 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TemplateHaskell #-}++-- | Tailwind runner in Haskell+--+-- TODO: Add `main` with CLI parser.+-- > tailwind-run [-w] 'src/**/*.hs'+module Web.Tailwind+  ( -- * Runner+    runTailwind,++    -- * Types+    TailwindConfig (..),+    Tailwind (..),+    Mode (..),+    tailwindConfig,+    tailwindInput,+    tailwindOutput,+    tailwindConfigContent,+    tailwindMode,+  )+where++import Control.Lens.TH (makeLenses)+import Control.Monad.Logger (MonadLogger, logInfoN)+import Data.Aeson (encode)+import Data.ByteString (hPut)+import Data.Default (Default (def))+import Deriving.Aeson+import NeatInterpolation (text)+import System.CPUTime (getCPUTime)+import System.Directory (doesFileExist)+import System.IO (hClose)+import System.Which (staticWhich)+import Text.Printf (printf)+import qualified Text.Show+import UnliftIO (MonadUnliftIO, finally)+import UnliftIO.Directory (removeFile)+import UnliftIO.Process (callProcess)+import UnliftIO.Temporary (withSystemTempFile)++-- | Haskell version of tailwind.config.js+--+-- Only the subset we care to define, as some fields (eg: plugins) are defined+-- with arbitrary JS code.+data TailwindConfig = TailwindConfig+  { -- | List of source patterns that reference CSS classes+    _tailwindConfigContent :: [FilePath]+  }+  deriving (Generic)+  deriving+    (ToJSON)+    via CustomJSON+          '[ FieldLabelModifier+               '[StripPrefix "_tailwindConfig", CamelToSnake]+           ]+          TailwindConfig++newtype Css = Css {unCss :: Text}++data Mode = JIT | Production+  deriving (Eq, Show)++data Tailwind = Tailwind+  { _tailwindConfig :: TailwindConfig,+    _tailwindInput :: Css,+    _tailwindOutput :: FilePath,+    _tailwindMode :: Mode+  }++makeLenses ''TailwindConfig+makeLenses ''Tailwind++instance Default TailwindConfig where+  def =+    TailwindConfig+      { _tailwindConfigContent = []+      }++instance Default Tailwind where+  def =+    Tailwind+      { _tailwindConfig = def,+        _tailwindInput = def,+        _tailwindOutput = "tailwind.css",+        _tailwindMode = JIT+      }++instance Default Css where+  def =+    Css+      [text|+      @tailwind base;+      @tailwind components;+      @tailwind utilities;+      |]++instance Text.Show.Show TailwindConfig where+  show (decodeUtf8 . encode -> config) =+    -- Use `Object.assign` to merge JSON (produced in Haskell) with the rest of+    -- config (defined by raw JS; that cannot be JSON encoded)+    toString+      [text|+      module.exports =+        Object.assign(+          JSON.parse('${config}'),+          {+            theme: {+              extend: {},+            },+            plugins: [+              require('@tailwindcss/typography'),+              require('@tailwindcss/forms'),+              require('@tailwindcss/line-clamp'),+              require('@tailwindcss/aspect-ratio')+            ],+          })+      |]++tailwind :: FilePath+tailwind = $(staticWhich "tailwind")++modeArgs :: Mode -> [String]+modeArgs = \case+  JIT -> ["-w"]+  Production -> ["--minify"]++runTailwind :: (MonadUnliftIO m, MonadLogger m, HasCallStack) => Tailwind -> m ()+runTailwind Tailwind {..} = do+  withTmpFile (show _tailwindConfig) $ \configFile ->+    withTmpFile (unCss _tailwindInput) $ \inputFile ->+      callTailwind $ ["-c", configFile, "-i", inputFile, "-o", _tailwindOutput] <> modeArgs _tailwindMode+  when (_tailwindMode == JIT) $+    error "Tailwind exited unexpectedly!"++withTmpFile :: MonadUnliftIO m => Text -> (FilePath -> m a) -> m a+withTmpFile s f = do+  withSystemTempFile "ema-tailwind-tmpfile" $ \fp h -> do+    liftIO $ do+      putStrLn $ "$ cat " <> fp+      putTextLn s+      hPut h (encodeUtf8 s) >> hClose h+    f fp+      `finally` removeFile fp++callTailwind :: (MonadIO m, MonadLogger m) => [String] -> m ()+callTailwind args = do+  logInfoN $ "Running Tailwind compiler with args: " <> show args+  liftIO (doesFileExist tailwind) >>= \case+    True ->+      timeIt $ do+        callProcess tailwind args+    False ->+      error $ "Tailwind compiler not found at " <> toText tailwind++timeIt :: MonadIO m => m b -> m b+timeIt m = do+  t0 <- liftIO getCPUTime+  !x <- m+  t1 <- liftIO getCPUTime+  let diff :: Double = fromIntegral (t1 - t0) / (10 ^ (9 :: Integer))+  liftIO $ printf "Process duration: %0.3f ms\n" diff+  pure $! x
+ tailwind.cabal view
@@ -0,0 +1,87 @@+cabal-version:      2.4+name:               tailwind+version:            0.1.0.0+license:            MIT+copyright:          2022 Sridhar Ratnakumar+maintainer:         srid@srid.ca+author:             Sridhar Ratnakumar+category:           Web++-- TODO: Before hackage release.+-- A short (one-line) description of the package.+synopsis:           Tailwind wrapped in Haskell++-- A longer description of the package.+description:        Run Tailwind from Haskell without touching JavaScript++-- A URL where users can report bugs.+-- bug-reports:++extra-source-files:+  CHANGELOG.md+  LICENSE+  README.md++common c+  mixins:+    base hiding (Prelude),+    relude (Relude as Prelude, Relude.Container.One),+    relude++  ghc-options:+    -Wall -Wincomplete-record-updates -Wincomplete-uni-patterns++  build-depends:+    , base          >=4.13.0.0 && <=4.17.0.0+    , data-default+    , filepath+    , filepattern+    , lens+    , monad-logger+    , relude++  default-extensions:+    FlexibleContexts+    FlexibleInstances+    KindSignatures+    LambdaCase+    MultiParamTypeClasses+    MultiWayIf+    OverloadedStrings+    ScopedTypeVariables+    TupleSections+    ViewPatterns++  default-language:   Haskell2010++library+  import:          c+  hs-source-dirs:  src+  exposed-modules: Web.Tailwind+  build-depends:+    , aeson+    , async+    , bytestring+    , containers+    , deriving-aeson+    , directory+    , mtl+    , neat-interpolation+    , profunctors+    , safe-exceptions+    , temporary+    , text+    , time+    , unliftio+    , which+    , with-utf8++executable tailwind-run+  import:         c+  hs-source-dirs: exe+  main-is:        Main.hs+  build-depends:+    , optparse-applicative+    , parser-combinators+    , tailwind+    , with-utf8