packages feed

cabal-gild-0.3.0.0: source/library/CabalGild/Action/Render.hs

module CabalGild.Action.Render where

import qualified CabalGild.Extra.FieldLine as FieldLine
import qualified CabalGild.Extra.Name as Name
import qualified CabalGild.Extra.SectionArg as SectionArg
import qualified CabalGild.Type.Block as Block
import qualified CabalGild.Type.Chunk as Chunk
import qualified CabalGild.Type.Comment as Comment
import qualified CabalGild.Type.Line as Line
import qualified Data.ByteString as ByteString
import qualified Distribution.Compat.Lens as Lens
import qualified Distribution.Fields as Fields

run ::
  (Applicative m) =>
  ([Fields.Field [Comment.Comment a]], [Comment.Comment a]) ->
  m ByteString.ByteString
run = pure . uncurry toByteString

toByteString ::
  [Fields.Field [Comment.Comment a]] ->
  [Comment.Comment a] ->
  ByteString.ByteString
toByteString fs cs =
  let i = 0 :: Int
   in Block.toByteString
        . Lens.set Block.lineBeforeLens False
        . Lens.set Block.lineAfterLens True
        $ fields i fs <> comments i cs

fields :: Int -> [Fields.Field [Comment.Comment a]] -> Block.Block
fields = foldMap . field

field :: Int -> Fields.Field [Comment.Comment a] -> Block.Block
field i f = case f of
  Fields.Field n fls -> case fls of
    [fl]
      | null $ FieldLine.annotation fl ->
          -- If the field only has one line and no comments, then it can be
          -- rendered all on one line.
          comments i (Name.annotation n)
            <> ( Block.fromLine
                   . Lens.over Line.chunkLens (mappend $ name n <> Chunk.colon)
                   $ fieldLine i fl
               )
    _ ->
      Lens.set Block.lineAfterLens True $
        comments i (Name.annotation n)
          <> Block.fromLine
            Line.Line
              { Line.indent = i,
                Line.chunk = name n <> Chunk.colon
              }
          <> fieldLines (i + 1) fls
  Fields.Section n sas fs ->
    Lens.set Block.lineBeforeLens True
      . Lens.set Block.lineAfterLens True
      $ comments i (Name.annotation n)
        -- Section arguments should never have comments in practice. This is
        -- here simply to ensure that they aren't lost.
        <> comments i (concatMap SectionArg.annotation sas)
        <> Block.fromLine
          Line.Line
            { Line.indent = i,
              Line.chunk = Lens.set Chunk.spaceAfterLens True (name n) <> sectionArgs sas
            }
        <> Lens.set Block.lineBeforeLens False (fields (i + 1) fs)

name :: Fields.Name a -> Chunk.Chunk
name = Chunk.fromByteString . Name.value

fieldLines :: Int -> [Fields.FieldLine [Comment.Comment a]] -> Block.Block
fieldLines = foldMap . fieldLineC

fieldLineC :: Int -> Fields.FieldLine [Comment.Comment a] -> Block.Block
fieldLineC i fl =
  comments i (FieldLine.annotation fl)
    <> Block.fromLine (fieldLine i fl)

fieldLine :: Int -> Fields.FieldLine a -> Line.Line
fieldLine i =
  Line.Line i
    . Lens.set Chunk.spaceBeforeLens True
    . Chunk.fromByteString
    . FieldLine.value

sectionArgs :: [Fields.SectionArg a] -> Chunk.Chunk
sectionArgs = Lens.set Chunk.spaceBeforeLens True . foldMap sectionArg

sectionArg :: Fields.SectionArg a -> Chunk.Chunk
sectionArg sa = case sa of
  Fields.SecArgName _ bs ->
    Lens.set Chunk.spaceBeforeLens True
      . Lens.set Chunk.spaceAfterLens True
      $ Chunk.fromByteString bs
  Fields.SecArgStr _ bs ->
    Lens.set Chunk.spaceBeforeLens True
      . Lens.set Chunk.spaceAfterLens True
      . Chunk.fromByteString
      . flip ByteString.snoc 0x22
      $ ByteString.cons 0x22 bs
  Fields.SecArgOther _ bs ->
    let b =
          bs /= ByteString.singleton 0x21 -- !
            && bs /= ByteString.singleton 0x28 -- (
            && bs /= ByteString.singleton 0x29 -- )
     in Lens.set Chunk.spaceBeforeLens b
          . Lens.set Chunk.spaceAfterLens b
          $ Chunk.fromByteString bs

comments :: Int -> [Comment.Comment a] -> Block.Block
comments i cs = mempty {Block.lines = fmap (comment i) cs}

comment :: Int -> Comment.Comment a -> Line.Line
comment i =
  Line.Line i
    . Chunk.fromByteString
    . mappend Comment.delimiter
    . Comment.value