packages feed

gongju-text-0.1.0.0: src/Gongju/Text/Layout.hs

{- |
Module      : Gongju.Text.Layout
Description : Lightweight layout, indentation, and block-nesting analysis
Copyright   : (c) Nadia Yvette Chambers, 2026
License     : EUPL-1.2 OR Apache-2.0

Provides indentation measuring and hierarchical block structure resolution
without relying on full specification codecs like Markdown.
-}
module Gongju.Text.Layout
    ( IndentedLine (..)
    , Block (..)
    , parseIndentedLines
    , parseBlocks
    ) where

import qualified Data.Text as T

-- | A line with measured leading whitespace indentation depth.
data IndentedLine = IndentedLine
    { indentDepth :: !Int
    , lineContent :: !T.Text
    } deriving stock (Show, Eq)

-- | A nested block of content based on indentation level.
data Block = Block
    { blockIndent :: !Int
    , blockLines  :: ![T.Text]
    , subBlocks   :: ![Block]
    } deriving stock (Show, Eq)

-- | Convert raw lines into 't:IndentedLine' records counting space depth.
parseIndentedLines :: [T.Text] -> [IndentedLine]
parseIndentedLines = map parseLine
  where
    parseLine t =
        let (spaces, rest) = T.span (== ' ') t
        in IndentedLine (T.length spaces) rest

-- | Group indented lines into nested hierarchical blocks.
parseBlocks :: [T.Text] -> [Block]
parseBlocks lines' = buildBlocks (parseIndentedLines $ filter (not . T.null . T.strip) lines')

buildBlocks :: [IndentedLine] -> [Block]
buildBlocks [] = []
buildBlocks (IndentedLine d content : rest) =
    let (sameOrDeeper, shallower) = span (\l -> indentDepth l >= d) rest
        (direct, deeper) = span (\l -> indentDepth l == d) sameOrDeeper
        currLines = content : map lineContent direct
        nested = buildBlocks deeper
        currBlock = Block d currLines nested
    in currBlock : buildBlocks shallower