packages feed

mdoc-0.2.0.0: src/Mdoc/Data/List.hs

-- |
--
-- Module      : Mdoc.Data.List
-- Copyright   : (c) 2026 Patrick Brisbin
-- License     : AGPL-3
-- Maintainer  : pbrisbin@gmail.com
-- Stability   : experimental
-- Portability : POSIX
module Mdoc.Data.List
  ( List
  , fromNonEmpty
  , singleton

    -- * Width
  , Width (..)
  , Indent
  , ByItem
  ) where

import Mdoc.Prelude

import Data.Aeson (object, (.=))
import Data.Kind (Constraint, Type)
import Data.List.NonEmpty qualified as NE
import Data.Text qualified as T
import Mdoc.Data.Described
import Mdoc.Pretty
import Mdoc.Syntax

type List :: forall {k}. k -> Type -> Type
newtype List t a = List
  { items :: NonEmpty (Described a)
  }
  deriving stock (Eq, Generic, Show)
  deriving (Semigroup) via Generically (List t a)

instance (Ord a, ToJSON a, Width t a) => ToJSON (List t a) where
  toJSON l =
    object
      [ "width" .= renderedWidth (Proxy @t) items
      , "items" .= items
      ]
   where
    items :: NonEmpty (Described a)
    items = NE.sort $ NE.nub l.items

fromNonEmpty :: NonEmpty (Described a) -> List t a
fromNonEmpty = foldMap1 singleton

singleton :: Described a -> List t a
singleton described = List {items = pure described}

data Indent

data ByItem

type Width :: forall {k}. k -> Type -> Constraint
class Width t a where
  renderedWidth :: Proxy t -> NonEmpty (Described a) -> MacroArg

instance Width Indent a where
  renderedWidth _ _ = "indent"

instance Pretty a => Width ByItem a where
  renderedWidth _ =
    Quoted
      . esc
      . maximumBy (comparing T.length)
      . fmap headWidth

headWidth :: Pretty a => Described a -> Text
headWidth = go . T.words . renderPlain . pretty . (.item)
 where
  go :: [Text] -> Text
  go = \case
    [] -> ""
    ("Ev" : rest) -> go rest
    ("Ns" : rest) -> go rest
    ("Ar" : rest) -> go rest
    ("Op" : rest) -> "[" <> go rest <> "]"
    ("Fl" : rest) -> "-" <> go rest
    ("|" : rest) -> "|" <> go rest -- assume Ns-wrapped
    ("," : rest) -> " , " <> go rest -- assume not Ns-wrapped
    (x : rest) -> x <> go rest